"""
cli.py — Click-based CLI for hallucination-grep.

Usage:
    hallucination-grep response.txt --codebase ./src
    echo "..." | hallucination-grep --codebase .
    hallucination-grep response.txt --codebase . --check functions,files
    hallucination-grep response.txt --codebase . --json
"""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import List, Optional

import click
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from rich import box
from rich.rule import Rule

from hallucination_grep.checker import CheckResult, HallucinationChecker, HallucinationReport
from hallucination_grep.extractor import ReferenceExtractor, ReferenceKind
from hallucination_grep.indexer import CodebaseIndexer

console = Console()
err_console = Console(stderr=True)

VALID_CHECKS = {"functions", "classes", "files", "imports", "methods", "variables"}

KIND_LABELS = {
    ReferenceKind.FUNCTION: "Function",
    ReferenceKind.CLASS: "Class",
    ReferenceKind.FILE: "File",
    ReferenceKind.IMPORT: "Import",
    ReferenceKind.METHOD: "Method",
    ReferenceKind.VARIABLE: "Variable",
}

KIND_COLORS = {
    ReferenceKind.FUNCTION: "cyan",
    ReferenceKind.CLASS: "green",
    ReferenceKind.FILE: "yellow",
    ReferenceKind.IMPORT: "blue",
    ReferenceKind.METHOD: "magenta",
    ReferenceKind.VARIABLE: "red",
}


@click.command(name="hallucination-grep")
@click.argument("input_file", required=False, type=click.Path(exists=True))
@click.option(
    "--codebase",
    "-c",
    required=True,
    type=click.Path(exists=True, file_okay=False, dir_okay=True),
    help="Path to the codebase directory to index and cross-check against.",
)
@click.option(
    "--check",
    "-k",
    default=None,
    help=(
        "Comma-separated list of check types to run. "
        "Options: functions, classes, files, imports, methods, variables. "
        "Default: all."
    ),
)
@click.option(
    "--json",
    "output_json",
    is_flag=True,
    default=False,
    help="Output results as JSON (useful for CI pipelines).",
)
@click.option(
    "--min-confidence",
    default=0.0,
    type=float,
    help="Only show hallucinations with confidence score below this threshold (0.0–1.0).",
)
@click.option(
    "--no-color",
    is_flag=True,
    default=False,
    help="Disable Rich color output.",
)
@click.version_option(version="0.1.0", prog_name="hallucination-grep")
def main(
    input_file: Optional[str],
    codebase: str,
    check: Optional[str],
    output_json: bool,
    min_confidence: float,
    no_color: bool,
) -> None:
    """
    Cross-check LLM output against your real codebase.

    Detects hallucinated function names, class names, file paths, and imports.

    INPUT_FILE: path to a file containing LLM output.
    If omitted, reads from stdin.

    Examples:

    \b
        hallucination-grep response.txt --codebase ./src
        echo "Use get_user_profile()..." | hallucination-grep --codebase .
        hallucination-grep response.txt --codebase . --check functions,classes
        hallucination-grep response.txt --codebase . --json
    """
    global console
    if no_color:
        console = Console(highlight=False, markup=False)

    # ------------------------------------------------------------------
    # 1. Read input text
    # ------------------------------------------------------------------
    if input_file:
        input_path = Path(input_file)
        try:
            input_text = input_path.read_text(encoding="utf-8", errors="replace")
        except OSError as exc:
            err_console.print(f"[red]Error reading input file:[/red] {exc}")
            sys.exit(2)
    else:
        if sys.stdin.isatty():
            err_console.print(
                "[yellow]Reading from stdin... (paste LLM output, then press Ctrl+D)[/yellow]"
            )
        try:
            input_text = sys.stdin.read()
        except KeyboardInterrupt:
            sys.exit(130)

    if not input_text.strip():
        err_console.print("[red]Error:[/red] Input text is empty.")
        sys.exit(2)

    # ------------------------------------------------------------------
    # 2. Parse check types
    # ------------------------------------------------------------------
    checks: Optional[List[str]] = None
    if check:
        checks = [c.strip().lower() for c in check.split(",")]
        invalid = set(checks) - VALID_CHECKS
        if invalid:
            err_console.print(
                f"[red]Invalid check type(s):[/red] {', '.join(sorted(invalid))}. "
                f"Valid options: {', '.join(sorted(VALID_CHECKS))}"
            )
            sys.exit(2)

    # ------------------------------------------------------------------
    # 3. Index the codebase (show progress unless JSON mode)
    # ------------------------------------------------------------------
    if not output_json:
        with console.status(
            "[bold blue]Indexing codebase...[/bold blue]", spinner="dots"
        ):
            indexer = CodebaseIndexer(codebase)
            index = indexer.build()
    else:
        indexer = CodebaseIndexer(codebase)
        index = indexer.build()

    # ------------------------------------------------------------------
    # 4. Extract references from LLM text
    # ------------------------------------------------------------------
    extractor = ReferenceExtractor(checks=checks)
    references = extractor.extract(input_text)

    # ------------------------------------------------------------------
    # 5. Cross-check against index
    # ------------------------------------------------------------------
    checker = HallucinationChecker(index, codebase)
    report = checker.check(references, input_text)

    # ------------------------------------------------------------------
    # 6. Output
    # ------------------------------------------------------------------
    if output_json:
        _output_json(report)
    else:
        _output_rich(report, no_color)

    # Exit code 1 if hallucinations found (useful for CI)
    if report.hallucination_count > 0:
        sys.exit(1)


# ---------------------------------------------------------------------------
# Rich (human-readable) output
# ---------------------------------------------------------------------------

def _output_rich(report: HallucinationReport, no_color: bool = False) -> None:
    console.print()
    console.print(
        Panel.fit(
            "[bold white]HALLUCINATION GREP REPORT[/bold white]",
            box=box.DOUBLE,
            border_style="bold blue",
            padding=(0, 2),
        )
    )
    console.print()

    # Summary line
    console.print(
        f"[dim]Scanned:[/dim] [bold]{report.input_lines}[/bold] lines of LLM output"
    )
    codebase_label = Path(report.codebase_root).name or report.codebase_root
    console.print(
        f"[dim]Codebase:[/dim] [bold]{codebase_label}[/bold] "
        f"([bold]{report.python_files_indexed}[/bold] Python files, "
        f"[bold]{report.total_files_indexed}[/bold] total files indexed)"
    )
    console.print()

    # ------------------------------------------------------------------
    # Hallucinations section
    # ------------------------------------------------------------------
    if report.hallucinations:
        count_str = f"[bold red]{report.hallucination_count}[/bold red]"
        console.print(f"[bold red]HALLUCINATIONS DETECTED:[/bold red] {count_str}")
        console.print()

        for result in report.hallucinations:
            _render_hallucination(result)

        console.print()
    else:
        console.print(
            "[bold green]No hallucinations detected.[/bold green] "
            "All references verified."
        )
        console.print()

    # ------------------------------------------------------------------
    # Verified section
    # ------------------------------------------------------------------
    if report.verified:
        console.print(
            f"[bold green]VERIFIED REFERENCES:[/bold green] "
            f"[bold green]{report.verified_count}[/bold green]"
        )
        _render_verified_list(report.verified)
        console.print()

    # ------------------------------------------------------------------
    # Summary line
    # ------------------------------------------------------------------
    console.print(Rule(style="dim"))
    rate_pct = int(report.hallucination_rate * 100)
    total = report.total_references

    if report.hallucination_count == 0:
        rate_color = "green"
    elif rate_pct < 20:
        rate_color = "yellow"
    else:
        rate_color = "red"

    console.print(
        f"[{rate_color}]Hallucination rate: {rate_pct}%[/{rate_color}] "
        f"({report.hallucination_count} of {total} code references)"
    )
    console.print()


def _render_hallucination(result: CheckResult) -> None:
    ref = result.reference
    kind_label = KIND_LABELS.get(ref.kind, ref.kind.value.capitalize())
    kind_color = KIND_COLORS.get(ref.kind, "white")

    # Build panel content
    lines: List[str] = []

    # Name line
    display_name = _format_display_name(ref)
    lines.append(
        f"  [bold {kind_color}]✗ {kind_label}:[/bold {kind_color}] "
        f"[bold white]{display_name}[/bold white]"
    )

    lines.append(
        f"    [dim]Mentioned in:[/dim] line {ref.line_number} of input"
    )
    lines.append(
        f"    [dim]Status:[/dim] [red]Does not exist in codebase[/red]"
    )

    if result.suggestion:
        lines.append(
            f"    [dim]Similar:[/dim] [yellow]{result.suggestion}[/yellow]"
        )
    else:
        lines.append(
            f"    [dim]Similar:[/dim] [dim]none found[/dim]"
        )

    content = "\n".join(lines)
    console.print(
        Panel(
            content,
            box=box.ROUNDED,
            border_style="red",
            padding=(0, 1),
        )
    )


def _render_verified_list(verified: List[CheckResult]) -> None:
    MAX_SHOW = 10
    shown = verified[:MAX_SHOW]
    hidden = len(verified) - len(shown)

    for result in shown:
        ref = result.reference
        kind_label = KIND_LABELS.get(ref.kind, ref.kind.value.capitalize())
        display_name = _format_display_name(ref)
        location = f" [dim]({result.found_at})[/dim]" if result.found_at else ""
        console.print(
            f"  [green]✓[/green] [dim]{kind_label}:[/dim] "
            f"[bold]{display_name}[/bold]{location}"
        )

    if hidden > 0:
        console.print(f"  [dim]... and {hidden} more[/dim]")


def _format_display_name(ref: "CodeReference") -> str:  # type: ignore[name-defined]
    if ref.kind == ReferenceKind.FUNCTION:
        return f"{ref.name}()"
    if ref.kind == ReferenceKind.METHOD:
        return f".{ref.name}()"
    if ref.kind == ReferenceKind.IMPORT:
        if ref.import_name:
            return f"from {ref.import_module} import {ref.import_name}"
        return f"import {ref.import_module}"
    return ref.name


# ---------------------------------------------------------------------------
# JSON output
# ---------------------------------------------------------------------------

def _output_json(report: HallucinationReport) -> None:
    def result_to_dict(r: CheckResult) -> dict:
        return {
            "kind": r.reference.kind.value,
            "name": r.reference.name,
            "line": r.reference.line_number,
            "raw": r.reference.raw_text,
            "verified": r.verified,
            "found_at": r.found_at,
            "suggestion": r.suggestion,
            "suggestion_name": r.suggestion_name,
            "confidence": r.confidence,
            **(
                {
                    "import_module": r.reference.import_module,
                    "import_name": r.reference.import_name,
                }
                if r.reference.kind == ReferenceKind.IMPORT
                else {}
            ),
        }

    output = {
        "summary": {
            "input_lines": report.input_lines,
            "codebase": report.codebase_root,
            "python_files_indexed": report.python_files_indexed,
            "total_files_indexed": report.total_files_indexed,
            "total_references": report.total_references,
            "hallucination_count": report.hallucination_count,
            "verified_count": report.verified_count,
            "hallucination_rate": round(report.hallucination_rate, 4),
        },
        "hallucinations": [result_to_dict(r) for r in report.hallucinations],
        "verified": [result_to_dict(r) for r in report.verified],
    }
    print(json.dumps(output, indent=2))


if __name__ == "__main__":
    main()
