"""Typer CLI entrypoint for pyguard."""
from __future__ import annotations

import asyncio
import json
import sys
from pathlib import Path
from typing import Optional

import typer
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn

from pyguard.models import Finding, ScanResult, Severity
from pyguard.reporters.json_reporter import render_json
from pyguard.reporters.terminal import render_terminal
from pyguard.scanner import Scanner
from pyguard.utils.config import ScanConfig

app = typer.Typer(
    help="pyguard — AI-powered PyPI supply chain security scanner",
    no_args_is_help=True,
)
console = Console(stderr=False)

FAIL_THRESHOLDS: dict[str, Severity] = {
    "critical": Severity.CRITICAL,
    "high": Severity.HIGH,
    "medium": Severity.MEDIUM,
    "low": Severity.LOW,
    "info": Severity.INFO,
}

SEVERITY_RANK: dict[Severity, int] = {
    Severity.CRITICAL: 5,
    Severity.HIGH: 4,
    Severity.MEDIUM: 3,
    Severity.LOW: 2,
    Severity.INFO: 1,
}


@app.callback()
def main() -> None:
    """pyguard — AI-powered PyPI supply chain security scanner."""


@app.command("scan")
def run_scan(
    target: str = typer.Argument(
        ...,
        help=(
            "Package spec (requests==2.31.0), bare name (requests), "
            "requirements file (-r path), or project directory (.)."
        ),
    ),
    requirements: Optional[Path] = typer.Option(  # noqa: UP007
        None,
        "--requirements",
        "-r",
        help="Path to a requirements.txt file to scan.",
        exists=True,
        file_okay=True,
        dir_okay=False,
    ),
    format: str = typer.Option("terminal", "--format", "-f", help="terminal | json | sarif"),
    output: Optional[Path] = typer.Option(  # noqa: UP007
        None,
        "--output",
        "-o",
        help="Write report to this file instead of stdout.",
    ),
    sandbox: bool = typer.Option(False, "--sandbox", help="Enable Docker sandbox layer (Layer 3)."),
    ai: bool = typer.Option(False, "--ai", help="Enable AI analysis layer (Layer 2)."),
    fail_on: Optional[str] = typer.Option(  # noqa: UP007
        None,
        "--fail-on",
        help="Exit 1 when findings meet or exceed this severity (critical|high|medium|low|info).",
    ),
    no_color: bool = typer.Option(False, "--no-color", help="Disable coloured output."),
) -> None:
    # -r flag overrides positional target
    effective_target = str(requirements) if requirements else target

    config = ScanConfig(
        target=effective_target,
        source_root=Path.cwd(),
        enable_ai=ai,
        enable_sandbox=sandbox,
    )
    scanner = Scanner(config)

    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        console=console,
        transient=True,
    ) as progress:
        task = progress.add_task(f"Scanning {effective_target}…", total=None)

        # Decide whether to scan one or many packages
        path = Path(effective_target)
        if path.exists() and (path.is_dir() or path.suffix == ".txt"):
            results: list[ScanResult] = asyncio.run(scanner.scan_many())
        else:
            results = [asyncio.run(scanner.scan())]

        progress.update(task, completed=True)

    exit_code = _exit_code(results, fail_on)

    if format == "json":
        report = json.dumps([render_json(r) for r in results], indent=2)
        _write_or_print(report, output)
    elif format == "sarif":
        from pyguard.reporters.sarif_reporter import render_sarif  # noqa: PLC0415

        report = json.dumps(render_sarif(results), indent=2)
        _write_or_print(report, output)
    elif format == "html":
        from pyguard.reporters.html_reporter import render_html  # noqa: PLC0415

        html = render_html(results)
        if output:
            output.write_text(html, encoding="utf-8")
            console.print(f"[green]HTML report written to {output}[/green]")
        else:
            console.print(html)
    else:
        # Terminal (default)
        _con = Console(no_color=no_color)
        for result in results:
            render_terminal(result, _con)

    raise typer.Exit(code=exit_code)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _exit_code(results: list[ScanResult], fail_on: str | None) -> int:
    if fail_on is None:
        return 0
    threshold = FAIL_THRESHOLDS.get(fail_on.lower())
    if threshold is None:
        console.print(
            f"[red]Unknown --fail-on value '{fail_on}'. "
            f"Valid: {', '.join(FAIL_THRESHOLDS)}[/red]"
        )
        return 2
    threshold_rank = SEVERITY_RANK[threshold]
    for result in results:
        for finding in result.findings:
            if SEVERITY_RANK[finding.severity] >= threshold_rank:
                return 1
    return 0


def _write_or_print(text: str, output: Path | None) -> None:
    if output:
        output.write_text(text, encoding="utf-8")
        console.print(f"[green]Report written to {output}[/green]")
    else:
        console.print(text)


if __name__ == "__main__":
    app()
