"""Diff command for comparing scan results."""

import json
from pathlib import Path
from typing import Annotated

import typer
from apiposture.core.models.scan_result import ScanResult
from rich.console import Console
from rich.table import Table

from apiposture_pro.features.diff_mode.analyzer import DiffAnalyzer
from apiposture_pro.licensing.manager import ProLicenseManager
from apiposture_pro.licensing.models import LicenseFeature, LicenseValidationResult

app = typer.Typer(help="Diff commands")
console = Console()


@app.command("diff")
def diff(
    baseline: Annotated[
        Path,
        typer.Argument(
            help="Baseline scan result file (JSON)",
            exists=True,
            file_okay=True,
            dir_okay=False,
        ),
    ],
    current: Annotated[
        Path,
        typer.Argument(
            help="Current scan result file (JSON)",
            exists=True,
            file_okay=True,
            dir_okay=False,
        ),
    ],
    summary_only: Annotated[
        bool,
        typer.Option(
            "--summary-only",
            help="Show only summary, not detailed changes",
        ),
    ] = False,
    fail_on_regression: Annotated[
        bool,
        typer.Option(
            "--fail-on-regression/--no-fail-on-regression",
            help="Exit with code 1 if regressions detected",
        ),
    ] = True,
    format: Annotated[
        str,
        typer.Option(
            "--format",
            "-f",
            help="Output format (console, json)",
        ),
    ] = "console",
    output_file: Annotated[
        Path | None,
        typer.Option(
            "--output-file",
            "-o",
            help="Write output to file instead of stdout",
        ),
    ] = None,
) -> None:
    """
    Compare two scan results and show differences.

    Requires valid Pro license.
    """
    # Validate license
    license_manager = ProLicenseManager()
    result = license_manager.validate()
    if not result.is_valid:
        _print_license_error(result)
        raise typer.Exit(1)

    if not license_manager.has_feature(LicenseFeature.DIFF):
        console.print("[red]✗[/red] 'diff' feature not included in your license")
        raise typer.Exit(1)

    try:
        # Load scan results
        console.print(f"[dim]Loading baseline: {baseline}[/dim]")
        baseline_result = _load_scan_result(baseline)

        console.print(f"[dim]Loading current: {current}[/dim]")
        current_result = _load_scan_result(current)

        # Perform diff
        console.print("[dim]Comparing results...[/dim]\n")
        analyzer = DiffAnalyzer()
        diff_result = analyzer.compare(baseline_result, current_result)

        # Display results
        if format == "json":
            json_output = _serialize_diff_result(diff_result, baseline, current)
            if output_file:
                output_file.write_text(json_output, encoding="utf-8")
                console.print(f"[green]✓[/green] Written to {output_file}")
            else:
                console.print(json_output)
        else:
            _display_diff_result(diff_result, summary_only)

        # Exit with error if regressions detected
        if fail_on_regression and diff_result.has_regressions:
            console.print(
                f"\n[red]✗[/red] Detected {diff_result.regression_count} regression(s)"
            )
            raise typer.Exit(1)

    except Exception as e:
        console.print(f"\n[red]✗[/red] Diff failed: {e}")
        raise typer.Exit(1) from e


def _serialize_diff_result(diff_result, baseline: Path, current: Path) -> str:
    """Serialize DiffResult to JSON string."""
    from datetime import datetime, timezone

    def _finding_to_dict(f) -> dict:
        ep = getattr(f, "endpoint", None)
        sev = (f.severity.value if hasattr(f.severity, "value") else str(f.severity)).upper()
        return {
            "rule_id": f.rule_id,
            "rule_name": f.rule_name,
            "severity": sev,
            "route": str(getattr(ep, "full_route", getattr(ep, "route", ""))) if ep else "",
            "file_path": str(ep.file_path) if ep else "",
            "line_number": ep.line_number if ep else 0,
            "message": f.message,
            "recommendation": f.recommendation or "",
        }

    severity_changes = []
    for sc in getattr(diff_result, "severity_changes", []):
        prev_sev = (sc.previous_severity.value if hasattr(sc.previous_severity, "value") else str(sc.previous_severity)).upper()
        curr_sev = (sc.current_severity.value if hasattr(sc.current_severity, "value") else str(sc.current_severity)).upper()
        severity_changes.append({
            "finding": _finding_to_dict(sc.finding),
            "previous_severity": prev_sev,
            "current_severity": curr_sev,
            "is_escalation": sc.is_escalation,
        })

    output = {
        "baseline_path": str(baseline),
        "current_path": str(current),
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "summary": {
            "baseline_findings": diff_result.baseline_scan_count,
            "current_findings": diff_result.current_scan_count,
            "new_findings": len(diff_result.new_findings),
            "resolved_findings": len(diff_result.resolved_findings),
            "new_endpoints": len(diff_result.new_endpoints),
            "removed_endpoints": len(diff_result.removed_endpoints),
            "has_regressions": diff_result.has_regressions,
            "regression_count": diff_result.regression_count,
        },
        "new_findings": [_finding_to_dict(f) for f in diff_result.new_findings],
        "resolved_findings": [_finding_to_dict(f) for f in diff_result.resolved_findings],
        "severity_changes": severity_changes,
        "new_endpoints": diff_result.new_endpoints,
        "removed_endpoints": diff_result.removed_endpoints,
    }

    return json.dumps(output, indent=2)


def _load_scan_result(file_path: Path) -> ScanResult:
    """Load scan result from JSON file."""
    with open(file_path) as f:
        data = json.load(f)

    # Reconstruct ScanResult from JSON
    # This is a simplified version - in production would use proper serialization
    from pathlib import Path as PathType

    from apiposture.core.models.endpoint import Endpoint
    from apiposture.core.models.enums import EndpointType, Framework, HttpMethod, Severity
    from apiposture.core.models.finding import Finding

    endpoints = []
    for ep_data in data.get("endpoints", []):
        # Convert method string(s) to HttpMethod enum.
        # JsonFormatter used to write methods as a comma-separated string (e.g. "GET, POST");
        # newer output writes a list. Handle both.
        methods_raw = ep_data.get("methods", ["GET"])
        if isinstance(methods_raw, str):
            methods = [HttpMethod[m.strip().upper()] for m in methods_raw.split(",") if m.strip()]
        else:
            methods = [HttpMethod[m.upper()] if isinstance(m, str) else m for m in methods_raw]
        if "method" in ep_data:  # Handle old format
            methods = [HttpMethod[ep_data["method"].upper()]]

        endpoints.append(
            Endpoint(
                route=ep_data.get("path", ep_data.get("route", "/")),
                methods=methods,
                file_path=PathType(ep_data.get("file_path", "unknown.py")),
                line_number=ep_data.get("line_number", 0),
                framework=Framework.FASTAPI,
                endpoint_type=EndpointType.ROUTER,
                function_name=ep_data.get("function_name", "unknown"),
            )
        )

    findings = []
    for f_data in data.get("findings", []):
        # Create a minimal endpoint for the finding
        endpoint = Endpoint(
            route="/",
            methods=[HttpMethod.GET],
            file_path=PathType(f_data.get("file_path", "unknown.py")),
            line_number=f_data.get("line_number", 0),
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="unknown",
        )

        findings.append(
            Finding(
                rule_id=f_data.get("rule_id", ""),
                rule_name=f_data.get("title", f_data.get("rule_name", "")),
                severity=Severity[f_data.get("severity", "INFO").upper()],
                message=f_data.get("description", f_data.get("message", "")),
                endpoint=endpoint,
                recommendation=f_data.get("recommendation", ""),
            )
        )

    return ScanResult(
        scan_path=PathType(
            data.get("project_path",
            data.get("scan_path",
            data.get("summary", {}).get("scanned_path", "/")))
        ),
        endpoints=endpoints,
        findings=findings,
    )


def _display_diff_result(diff_result, summary_only: bool) -> None:
    """Display diff result to console."""
    # Show summary
    console.print("[bold]Diff Summary[/bold]\n")
    console.print(diff_result.summary)

    if summary_only:
        return

    # Show detailed changes
    if diff_result.new_findings:
        console.print("\n[bold red]New Findings:[/bold red]")
        _display_findings_table(diff_result.new_findings)

    if diff_result.resolved_findings:
        console.print("\n[bold green]Resolved Findings:[/bold green]")
        _display_findings_table(diff_result.resolved_findings)

    # Show severity changes (Phase 5)
    if hasattr(diff_result, "severity_changes") and diff_result.severity_changes:
        console.print("\n[bold magenta]Severity Changes:[/bold magenta]")
        sev_table = Table(show_header=True)
        sev_table.add_column("Rule ID", style="magenta")
        sev_table.add_column("Location", style="dim")
        sev_table.add_column("Previous", style="cyan")
        sev_table.add_column("", style="white")
        sev_table.add_column("Current", style="cyan")
        sev_table.add_column("Direction")

        for change in diff_result.severity_changes:
            prev_sev = change.previous_severity.value if hasattr(change.previous_severity, "value") else str(change.previous_severity)
            curr_sev = change.current_severity.value if hasattr(change.current_severity, "value") else str(change.current_severity)

            if change.is_escalation:
                direction = "[red]ESCALATION[/red]"
                curr_display = f"[red]{curr_sev.upper()}[/red]"
            else:
                direction = "[green]DE-ESCALATION[/green]"
                curr_display = f"[green]{curr_sev.upper()}[/green]"

            file_path = "unknown"
            line_number = 0
            if hasattr(change.finding, "endpoint") and change.finding.endpoint:
                file_path = str(change.finding.endpoint.file_path)
                line_number = change.finding.endpoint.line_number

            sev_table.add_row(
                change.finding.rule_id,
                f"{file_path}:{line_number}",
                prev_sev.upper(),
                "->",
                curr_display,
                direction,
            )

        console.print(sev_table)

    if diff_result.new_endpoints:
        console.print("\n[bold cyan]New Endpoints:[/bold cyan]")
        for endpoint in diff_result.new_endpoints:
            console.print(f"  + {endpoint}")

    if diff_result.removed_endpoints:
        console.print("\n[bold yellow]Removed Endpoints:[/bold yellow]")
        for endpoint in diff_result.removed_endpoints:
            console.print(f"  - {endpoint}")


def _display_findings_table(findings) -> None:
    """Display findings in a table."""
    table = Table(show_header=True)
    table.add_column("Severity", style="cyan")
    table.add_column("Rule ID", style="magenta")
    table.add_column("Title")
    table.add_column("Location", style="dim")

    for finding in findings[:10]:  # Limit to first 10
        severity_value = finding.severity.value if hasattr(finding.severity, "value") else str(finding.severity)
        severity_color = {
            "critical": "red",
            "high": "red",
            "medium": "yellow",
            "low": "blue",
            "info": "dim",
        }.get(severity_value.lower(), "white")

        # Get file path from endpoint
        file_path = "unknown"
        line_number = 0
        if hasattr(finding, "endpoint") and finding.endpoint:
            file_path = str(finding.endpoint.file_path)
            line_number = finding.endpoint.line_number

        table.add_row(
            f"[{severity_color}]{severity_value.upper()}[/{severity_color}]",
            finding.rule_id,
            finding.rule_name,
            f"{file_path}:{line_number}",
        )

    console.print(table)

    if len(findings) > 10:
        console.print(f"[dim]  ... and {len(findings) - 10} more[/dim]")


def _print_license_error(result: LicenseValidationResult) -> None:
    """Print error-code-specific license error message."""
    from apiposture_pro.licensing.models import LicenseErrorCode

    messages = {
        LicenseErrorCode.EXPIRED: f"[red]✗[/red] Your license has expired. {result.error_message}",
        LicenseErrorCode.MACHINE_MISMATCH: "[red]✗[/red] License is bound to a different machine. Re-activate or contact support.",
        LicenseErrorCode.REVOKED: "[red]✗[/red] Your license has been revoked. Contact support for assistance.",
        LicenseErrorCode.TOO_MANY_ACTIVATIONS: "[red]✗[/red] Too many activations for this license key.",
        LicenseErrorCode.NETWORK_ERROR: "[red]✗[/red] License could not be validated offline. Connect to the internet to re-validate.",
        LicenseErrorCode.GRACE_PERIOD_EXPIRED: "[red]✗[/red] License expired and grace period has ended. Renew your license.",
        LicenseErrorCode.INVALID_SIGNATURE: "[red]✗[/red] License key has an invalid signature.",
    }

    msg = messages.get(result.error_code, f"[red]✗[/red] No valid license found. {result.error_message}")
    console.print(msg)
    console.print("\nActivate your license with: apiposture-pro activate <key>")
