"""CLI implementation for `antiqa run JIRA-123` — the full pipeline."""

from __future__ import annotations

import json

import typer
from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.table import Table
from rich.text import Text

from antiqa.config.manager import config_exists, load_config

console = Console()


def run_pipeline(
    ticket_key: str,
    dry_run: bool = False,
    record: bool = False,
    headed: bool | None = None,
    skip_jira_updates: bool = False,
) -> None:
    """Execute the full AntiQA pipeline for a Jira ticket."""
    if not config_exists():
        console.print("[red]No antiqa.json found. Run [bold]antiqa init[/bold] first.[/red]")
        raise typer.Exit(code=2)

    config = load_config()
    if headed is not None:
        config.browser.headless = not headed

    from antiqa.jira.agent import JiraAgent
    from antiqa.pipeline.orchestrator import Pipeline

    console.print()
    console.print(Panel(
        f"[bold cyan]AntiQA[/bold cyan]  ·  Running tests for [bold]{ticket_key}[/bold]\n"
        f"[dim]Target: {config.app_url}  |  LLM: {config.llm.provider}/{config.llm.model}[/dim]",
        border_style="cyan",
        expand=False,
    ))
    console.print()

    # Connect to Jira
    try:
        with console.status("[cyan]Connecting to Jira..."):
            jira = JiraAgent(config)
    except RuntimeError as exc:
        console.print(f"[red]Jira connection failed:[/red] {exc}")
        raise typer.Exit(code=2)

    pipeline = Pipeline(
        config=config,
        jira_agent=jira,
        headed=headed,
        record_video=record,
        on_progress=_make_progress_callback(),
    )

    # ── Dry run ────────────────────────────────────────
    if dry_run:
        console.print("[bold yellow]DRY RUN[/bold yellow] — showing plan without executing\n")
        with console.status("[cyan]Building test plan..."):
            plan = pipeline.dry_run(ticket_key)

        if "error" in plan:
            console.print(f"[red]Error:[/red] {plan['error']}")
            raise typer.Exit(code=2)

        _render_dry_run(plan)
        return

    # ── Full run ───────────────────────────────────────
    result = pipeline.run(ticket_key)

    console.print()
    _render_result(result, skip_jira_updates)

    raise typer.Exit(code=result.exit_code)


def run_local_pipeline(
    files: list[str],
    record: bool = False,
    headed: bool | None = None,
) -> None:
    """Execute tests from local Excel/CSV files (no Jira needed)."""
    if not config_exists():
        console.print("[red]No antiqa.json found. Run [bold]antiqa init[/bold] first.[/red]")
        raise typer.Exit(code=2)

    config = load_config()
    if headed is not None:
        config.browser.headless = not headed

    from pathlib import Path

    from antiqa.agents.test_case_interpreter import TestCaseInterpreterAgent
    from antiqa.browser.manager import BrowserManager
    from antiqa.crawler.graph import NavigationGraph
    from antiqa.flows.runner import FlowRunner
    from antiqa.jira.test_case_parser import TestCaseFileParser
    from antiqa.pipeline.result import ExecutionPath, PipelineResult

    console.print()
    console.print(Panel(
        f"[bold cyan]AntiQA[/bold cyan]  ·  Local test run\n"
        f"[dim]Target: {config.app_url}  |  Files: {len(files)}[/dim]",
        border_style="cyan",
        expand=False,
    ))
    console.print()

    # Parse test cases
    parser = TestCaseFileParser()
    paths = [Path(f) for f in files]
    for p in paths:
        if not p.exists():
            console.print(f"[red]File not found: {p}[/red]")
            raise typer.Exit(code=2)

    with console.status("[cyan]Parsing test case files..."):
        cases = parser.parse_many(paths)

    if not cases:
        console.print("[yellow]No test cases found in the provided files.[/yellow]")
        raise typer.Exit(code=0)

    console.print(f"  [green]✓[/green] Parsed {len(cases)} test case(s)")

    # Load graph if available
    graph = None
    graph_path = Path("logs/navigation_graph.json")
    if graph_path.is_file():
        graph = NavigationGraph.load(graph_path)

    # AI interpret
    with console.status("[cyan]AI interpreting test cases..."):
        interpreter = TestCaseInterpreterAgent(config, graph=graph)
        flows = interpreter.interpret_many(cases)

    console.print(f"  [green]✓[/green] Generated {len(flows)} executable flow(s)")

    if not flows:
        console.print("[yellow]No flows could be generated.[/yellow]")
        raise typer.Exit(code=0)

    # Execute
    result = PipelineResult(ticket_key="LOCAL", execution_path=ExecutionPath.TEST_CASES)
    mgr = BrowserManager(config.browser, logs_dir=config.logs_dir)
    page = mgr.launch(record_video=record)

    try:
        runner = FlowRunner(page, config=config, screenshots_dir=f"{config.logs_dir}/screenshots")
        for i, flow in enumerate(flows, 1):
            console.print(f"\n  [cyan]▶ Running[/cyan] {flow.name} ({i}/{len(flows)})")
            fr = runner.run(flow, stop_on_failure=True)
            result.flow_results.append(fr)
            status = "[green]PASS[/green]" if fr.passed else "[red]FAIL[/red]"
            health_note = ""
            if fr.health_issues:
                health_note = f", [yellow]{len(fr.health_issues)} health issue(s)[/yellow]"
            console.print(f"    {status} — {len(fr.steps)} steps, {fr.duration_ms:.0f}ms{health_note}")
    finally:
        mgr.close()

    from antiqa.utils.cleanup import remove_screenshots
    remove_screenshots(f"{config.logs_dir}/screenshots")

    console.print()
    _render_result(result, skip_jira=True)
    raise typer.Exit(code=result.exit_code)


# ── Rendering helpers ──────────────────────────────────

def _make_progress_callback():
    """Return a progress callback that prints status updates."""
    icons = {
        "fetch": "📋", "attachments": "📎", "download": "⬇️",
        "parse": "📄", "interpret": "🤖", "analyze": "🔍",
        "browser": "🌐", "load": "📂", "execute": "▶️",
        "analysis": "🐛", "bug": "🪲", "report": "✅",
        "health": "💊", "cleanup": "🧹",
    }

    def callback(phase: str, detail: str) -> None:
        icon = icons.get(phase, "·")
        console.print(f"  {icon} {detail}")

    return callback


def _render_health_issues(issues: list) -> None:
    """Render health issues as a Rich table."""
    from antiqa.browser.health import HealthIssue, IssueSeverity

    severity_style = {
        IssueSeverity.CRITICAL: "[red]CRITICAL[/red]",
        IssueSeverity.WARNING: "[yellow]WARNING[/yellow]",
        IssueSeverity.INFO: "[dim]INFO[/dim]",
    }

    category_icons = {
        "console_error": "🖥️",
        "js_exception": "💥",
        "api_failure": "🔌",
        "network_error": "🌐",
        "page_crash": "💀",
        "performance": "⚡",
        "security": "🔒",
    }

    table = Table(
        title="Runtime Health Issues",
        show_lines=True,
        title_style="bold yellow",
    )
    table.add_column("", width=3)
    table.add_column("Severity", width=10)
    table.add_column("Category")
    table.add_column("Message", max_width=70)
    table.add_column("URL", style="dim", max_width=40)

    for issue in issues:
        icon = category_icons.get(issue.category.value, "·")
        sev = severity_style.get(issue.severity, str(issue.severity.value))
        url = issue.url
        if len(url) > 40:
            url = "..." + url[-37:]
        table.add_row(
            icon,
            sev,
            issue.category.value.replace("_", " "),
            issue.message[:70],
            url,
        )

    console.print(table)


def _render_dry_run(plan: dict) -> None:
    """Pretty-print a dry-run plan."""
    console.print(Panel(
        f"[bold]{plan.get('summary', '')}[/bold]",
        title=f"[cyan]{plan['ticket']}[/cyan]",
        border_style="yellow",
        expand=False,
    ))

    path = plan.get("path", "unknown")
    console.print(f"\n[dim]Execution path:[/dim] [bold]{path}[/bold]\n")

    if path == "test_cases":
        if plan.get("attachments"):
            console.print("[dim]Attachments:[/dim]")
            for a in plan["attachments"]:
                console.print(f"  • {a}")
            console.print()

        if plan.get("test_cases"):
            table = Table(title="Test Cases", show_lines=True)
            table.add_column("ID", style="cyan")
            table.add_column("Title")
            table.add_column("Steps", justify="right")
            for tc in plan["test_cases"]:
                table.add_row(tc["id"], tc["title"], str(tc["steps"]))
            console.print(table)
            console.print()

        if plan.get("flows"):
            table = Table(title="Generated Flows (to execute)", show_lines=True)
            table.add_column("Name", style="cyan")
            table.add_column("Description")
            table.add_column("Steps", justify="right")
            for f in plan["flows"]:
                table.add_row(f["name"], f.get("description", ""), str(f["steps"]))
            console.print(table)

    elif path == "ticket_analysis":
        tp = plan.get("test_plan", {})
        console.print(f"[dim]Reasoning:[/dim] {tp.get('reasoning', 'N/A')}")
        console.print(f"\n[dim]Flows to run:[/dim] {', '.join(tp.get('flows', []))}")
        if tp.get("risk_areas"):
            console.print(f"[dim]Risk areas:[/dim] {', '.join(tp['risk_areas'])}")

    console.print()
    console.print("[yellow]No tests were executed (dry run).[/yellow]")


def _render_result(result, skip_jira: bool = False) -> None:
    """Render the full pipeline result to the terminal."""
    from antiqa.pipeline.result import PipelineResult

    if not isinstance(result, PipelineResult):
        return

    # Error case
    if result.error:
        console.print(Panel(
            f"[bold red]EXECUTION ERROR[/bold red]\n\n{result.error}",
            border_style="red",
            expand=False,
        ))
        return

    # Flow results table
    if result.flow_results:
        table = Table(title="Flow Results", show_lines=True, title_style="bold")
        table.add_column("Flow", style="cyan")
        table.add_column("Steps", justify="right")
        table.add_column("Passed", justify="right")
        table.add_column("Failed", justify="right")
        table.add_column("Health", justify="right")
        table.add_column("Duration", justify="right")
        table.add_column("Status")

        for fr in result.flow_results:
            passed = sum(1 for s in fr.steps if s.passed)
            failed = sum(1 for s in fr.steps if not s.passed)
            status = "[green]PASS[/green]" if fr.passed else "[red]FAIL[/red]"
            health_count = len(fr.health_issues)
            health_str = str(health_count) if health_count == 0 else f"[yellow]{health_count}[/yellow]"
            if fr.critical_health_issues:
                health_str = f"[red]{health_count}[/red]"
            table.add_row(
                fr.flow_name,
                str(len(fr.steps)),
                str(passed),
                str(failed),
                health_str,
                f"{fr.duration_ms:.0f}ms",
                status,
            )
        console.print(table)

    # Failed step details
    for fr in result.flow_results:
        if not fr.passed:
            console.print()
            detail_table = Table(
                title=f"[red]Failed Steps — {fr.flow_name}[/red]",
                show_lines=True,
            )
            detail_table.add_column("#", width=4, justify="right")
            detail_table.add_column("Action")
            detail_table.add_column("Description")
            detail_table.add_column("Error", style="red")

            for step in fr.failed_steps:
                detail_table.add_row(
                    str(step.step_number),
                    step.action,
                    step.description[:50],
                    (step.action_result.error or "")[:80],
                )
            console.print(detail_table)

    # Health issues (deduplicated across flows for cleaner output)
    all_health = result.all_health_issues
    if all_health:
        seen: set[str] = set()
        unique: list = []
        for issue in all_health:
            key = f"{issue.category.value}:{issue.message}"
            if key not in seen:
                seen.add(key)
                unique.append(issue)
        console.print()
        _render_health_issues(unique)

    # Bugs filed
    if result.bugs_filed:
        console.print()
        bug_table = Table(title="Bugs Filed in Jira", show_lines=True, title_style="bold red")
        bug_table.add_column("Key", style="cyan")
        bug_table.add_column("Summary")
        bug_table.add_column("Severity")
        bug_table.add_column("Flow")

        for bug in result.bugs_filed:
            bug_table.add_row(bug.jira_key, bug.summary[:60], bug.severity, bug.flow_name)
        console.print(bug_table)

    # Summary panel
    console.print()
    s = result.summary()
    health_line = ""
    if s["health_issues"] > 0:
        health_line = f"\nHealth: [red]{s['health_critical']} critical[/red], [yellow]{s['health_warnings']} warnings[/yellow]"

    if result.all_passed:
        console.print(Panel(
            f"[bold green]ALL TESTS PASSED[/bold green]  ✅\n\n"
            f"Flows: {s['flows_passed']}/{s['flows_total']}  |  "
            f"Steps: {s['steps_passed']}/{s['steps_total']}  |  "
            f"Duration: {s['duration_ms']:.0f}ms"
            f"{health_line}\n"
            f"[dim]Path: {s['path']}  |  Ticket: {s['ticket']}[/dim]",
            border_style="green" if not all_health else "yellow",
            expand=False,
        ))
    else:
        console.print(Panel(
            f"[bold red]TESTS FAILED[/bold red]  ❌\n\n"
            f"Flows: {s['flows_passed']}/{s['flows_total']} passed, "
            f"[red]{s['flows_failed']} failed[/red]  |  "
            f"Steps: {s['steps_passed']}/{s['steps_total']}  |  "
            f"Duration: {s['duration_ms']:.0f}ms\n"
            f"Bugs filed: {s['bugs_filed']}"
            f"{health_line}\n"
            f"[dim]Path: {s['path']}  |  Ticket: {s['ticket']}[/dim]",
            border_style="red",
            expand=False,
        ))
