"""Flightline Discover command - AI operation discovery."""

import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

import click

from flightline import __version__
from flightline.discover.diff import compare_discoveries
from flightline.discover.ingest import (
    build_repo_metadata,
    detect_project_signals,
    enumerate_files,
)
from flightline.discover.interpret import interpret_all, sort_by_risk
from flightline.discover.observe import observe_all
from flightline.discover.schema import Artifact, DiscoveryOutput, SourceType
from flightline.hud import (
    console,
    create_status_spinner,
    print_boot_sequence,
    print_complete,
    print_info,
    print_status,
    print_success,
    print_target,
    print_warning,
)


def _format_discovery_output(output: DiscoveryOutput, indent: int = 2) -> str:
    """Format discovery output as JSON."""
    return output.model_dump_json(indent=indent)


def _get_repo_commit(path: Path) -> Optional[str]:
    """Get the current commit hash of the repo."""
    try:
        return subprocess.check_output(
            ["git", "rev-parse", "HEAD"], 
            cwd=path, 
            stderr=subprocess.DEVNULL
        ).decode().strip()
    except Exception:
        return None


def _print_discovery_summary(output: DiscoveryOutput) -> None:
    """Print MFD-style discovery summary to console."""
    from rich.text import Text

    from flightline.discover.schema import DetectionMethod
    
    # Check for baseline diffs
    has_diff = hasattr(output, "_diff") and output._diff
    
    # ... existing counts logic ...
    provider_counts: dict[str, int] = {}
    for node in output.nodes:
        provider = node.provider.value
        provider_counts[provider] = provider_counts.get(provider, 0) + 1
    
    # Count by detection method
    method_counts: dict[str, int] = {
        DetectionMethod.SDK_VERIFIED: 0,
        DetectionMethod.HEURISTIC: 0,
    }
    for node in output.nodes:
        method_counts[node.detection_method] = method_counts.get(node.detection_method, 0) + 1
    
    # Count input sources
    source_counts: dict[str, int] = {}
    for node in output.nodes:
        if node.inputs.system_prompt:
            src = node.inputs.system_prompt.source_type.value
            source_counts[src] = source_counts.get(src, 0) + 1
        for cv in node.inputs.context_variables:
            src = cv.source_type.value
            source_counts[src] = source_counts.get(src, 0) + 1
    
    # Diff summary line
    if has_diff:
        diff = output._diff
        diff_parts = []
        if diff["new"]:
            diff_parts.append(f"+{len(diff['new'])} NEW")
        if diff["removed"]:
            diff_parts.append(f"-{len(diff['removed'])} REMOVED")
        if diff["risk_changes"]:
            diff_parts.append(f"Δ {len(diff['risk_changes'])} RISK CHANGES")
            
        if diff_parts:
            print_info("CHANGES FROM BASELINE: " + " | ".join(diff_parts))
            console.print()

    # Detection summary box
    box_width = 62
    inner_width = box_width - 4
    
    text = Text()
    text.append("╔" + "═" * (box_width - 2) + "╗\n", style="hud.frame")
    
    # Title line
    title = f"◉ DETECTED {len(output.nodes)} AI OPERATIONS"
    title_padding = inner_width - len(title)
    text.append("║  ", style="hud.frame")
    text.append(title, style="hud.bright")
    text.append(" " * title_padding + "║\n", style="hud.frame")
    
    # Provider breakdown
    for provider, count in sorted(provider_counts.items()):
        line = f"    ├─ {provider.upper()}: {count}"
        padding = inner_width - len(line)
        text.append("║  ", style="hud.frame")
        text.append(line, style="hud")
        text.append(" " * padding + "║\n", style="hud.frame")
    
    # Method breakdown
    verified = method_counts.get(DetectionMethod.SDK_VERIFIED, 0)
    candidates = method_counts.get(DetectionMethod.HEURISTIC, 0)
    
    if verified:
        line = f"    ├─ SDK VERIFIED: {verified}"
        padding = inner_width - len(line)
        text.append("║  ", style="hud.frame")
        text.append(line, style="hud.bright")
        text.append(" " * padding + "║\n", style="hud.frame")
        
    if candidates:
        line = f"    └─ CANDIDATES: {candidates}"
        padding = inner_width - len(line)
        text.append("║  ", style="hud.frame")
        text.append(line, style="hud.warning")
        text.append(" " * padding + "║\n", style="hud.frame")
    
    text.append("╚" + "═" * (box_width - 2) + "╝", style="hud.frame")
    
    console.print()
    console.print(text)
    console.print()
    
    # Top risks section
    if output.nodes:
        sorted_nodes = sort_by_risk(output.nodes)
        top_nodes = sorted_nodes[:5]  # Show top 5
        
        text = Text()
        text.append("┌" + "─" * (box_width - 2) + "┐\n", style="hud.frame")
        
        header = "TOP PRIORITY (scenario testing recommended)"
        header_padding = inner_width - len(header)
        text.append("│  ", style="hud.frame")
        text.append(header, style="hud.bright")
        text.append(" " * header_padding + "│\n", style="hud.frame")
        
        text.append("├" + "─" * (box_width - 2) + "┤\n", style="hud.frame")
        
        for i, node in enumerate(top_nodes):
            # Node header
            func_name = node.location.function or "module"
            tier = node.interpretation.risk_tier.value.upper() if node.interpretation else "?"
            
            tier_style = "hud.error" if tier == "CRITICAL" else "hud.warning" if tier == "HIGH" else "hud"
            
            line1 = f"{i + 1}. {func_name}"
            tier_badge = f"[{tier}]"
            line1_padding = inner_width - len(line1) - len(tier_badge)
            
            text.append("│  ", style="hud.frame")
            text.append(line1, style="hud.bright")
            text.append(" " * line1_padding, style="hud")
            text.append(tier_badge, style=tier_style)
            text.append("│\n", style="hud.frame")
            
            # Location
            loc_line = f"   {node.location.file}:{node.location.line}"
            loc_padding = inner_width - len(loc_line)
            text.append("│  ", style="hud.frame")
            text.append(loc_line, style="hud.dim")
            text.append(" " * loc_padding + "│\n", style="hud.frame")
            
            # Key observations (show first 3)
            if node.interpretation:
                for obs_text in node.interpretation.observations_summary[:3]:
                    obs_line = f"   ├─ {obs_text}"
                    if len(obs_line) > inner_width:
                        obs_line = obs_line[:inner_width - 3] + "..."
                    obs_padding = inner_width - len(obs_line)
                    text.append("│  ", style="hud.frame")
                    text.append(obs_line, style="hud")
                    text.append(" " * max(0, obs_padding) + "│\n", style="hud.frame")
            
            # Sinks summary (one line)
            if node.usage.sinks:
                sinks_text = "   └─ Sinks: " + " | ".join([s.value.upper() for s in node.usage.sinks])
                if len(sinks_text) > inner_width:
                    sinks_text = sinks_text[:inner_width - 3] + "..."
                sinks_padding = inner_width - len(sinks_text)
                text.append("│  ", style="hud.frame")
                text.append(sinks_text, style="hud.dim")
                text.append(" " * max(0, sinks_padding) + "│\n", style="hud.frame")

            # Blank line between nodes
            if i < len(top_nodes) - 1:
                text.append("│  " + " " * inner_width + "│\n", style="hud.frame")
        
        text.append("└" + "─" * (box_width - 2) + "┘", style="hud.frame")
        
        console.print(text)
        console.print()
    
    # Input sources summary
    if source_counts:
        external_sources = {
            k: v for k, v in source_counts.items()
            if k in {"api", "database", "user_input", "config"}
        }
        
        if external_sources:
            print_info("INPUT SOURCES DETECTED:")
            for src, count in sorted(external_sources.items(), key=lambda x: -x[1]):
                source_label = {
                    "api": "from external APIs",
                    "database": "from database",
                    "user_input": "from user input",
                    "config": "from config/env",
                }.get(src, f"from {src}")
                print_info(f"  • {count} {source_label}")
            console.print()


@click.command()
@click.argument(
    "path",
    type=click.Path(exists=True, path_type=Path),
    default=".",
)
@click.option(
    "--out", "-o",
    type=click.Path(path_type=Path),
    default="flightline.discovery.json",
    help="Output file path",
)
@click.option(
    "--languages",
    type=str,
    default=None,
    help="Languages to scan (comma-separated: ts,js,py). Default: auto-detect",
)
@click.option(
    "--max-files",
    type=int,
    default=5000,
    help="Maximum files to scan",
)
@click.option(
    "--json",
    "json_only",
    is_flag=True,
    help="Output JSON only, no console summary",
)
@click.option(
    "--no-interpret",
    is_flag=True,
    help="Skip interpretation layer (observations only)",
)
@click.option(
    "--baseline",
    type=click.Path(exists=True, path_type=Path),
    help="Baseline discovery JSON to compare against",
)
def discover(
    path: Path,
    out: Path,
    languages: Optional[str],
    max_files: int,
    json_only: bool,
    no_interpret: bool,
    baseline: Optional[Path] = None,
):
    """Discover AI operations in a codebase.
    
    Scans PATH for AI SDK calls (OpenAI, Anthropic) and produces
    a discovery map with input sources, output flows, and risk assessment.
    
    \b
    Examples:
      flightline discover
      flightline discover ./src -o discovery.json
      flightline discover --languages ts,py --max-files 1000
    """
    # Resolve path
    root = path.resolve()
    
    if not json_only:
        print_boot_sequence("DISCOVER")
        print_status("INITIALIZING", "RDY")
        console.print()
    
    # Parse language filter
    language_filter = None
    if languages:
        language_filter = set(lang.strip().lower() for lang in languages.split(","))
        # Normalize
        normalized = set()
        for lang in language_filter:
            if lang in {"ts", "typescript"}:
                normalized.add("typescript")
            elif lang in {"js", "javascript"}:
                normalized.add("javascript")
            elif lang in {"py", "python"}:
                normalized.add("python")
            else:
                normalized.add(lang)
        language_filter = normalized
    
    # Ingest files
    if not json_only:
        print_target("SCAN PATH", str(root), wp_num=1)
    
    with create_status_spinner("INDEXING FILES") if not json_only else nullcontext():
        file_index = enumerate_files(root, max_files=max_files, languages=language_filter)
    
    if not json_only:
        lang_str = ", ".join(sorted(file_index.languages)).upper() or "NONE"
        print_info(f"FILES: {len(file_index.files)}  LANGUAGES: {lang_str}")
        console.print()
    
    if not file_index.files:
        if not json_only:
            print_warning("NO SUPPORTED FILES FOUND")
            print_info("SUPPORTED: .py, .js, .ts, .tsx, .jsx")
        return
    
    # Detect project signals
    with create_status_spinner("DETECTING PROJECT TYPE") if not json_only else nullcontext():
        project_signals = detect_project_signals(root)
    
    # Observe AI calls
    with create_status_spinner("SCANNING FOR AI OPERATIONS") if not json_only else nullcontext():
        observations = observe_all(file_index)
    
    if not json_only:
        if not observations:
            print_warning("NO AI OPERATIONS DETECTED")
            print_info("LOOKING FOR: OpenAI, Anthropic SDK calls")
        else:
            print_success(f"FOUND {len(observations)} AI OPERATIONS")
    
    # Interpret observations
    if observations and not no_interpret:
        with create_status_spinner("ANALYZING RISK") if not json_only else nullcontext():
            observations = interpret_all(observations)
    
    # Collect artifacts (prompt sources, etc.)
    artifacts: list[Artifact] = []
    seen_sources: set[str] = set()
    
    for obs in observations:
        if obs.inputs.system_prompt and obs.inputs.system_prompt.source_hint:
            hint = obs.inputs.system_prompt.source_hint
            if hint not in seen_sources and obs.inputs.system_prompt.source_type == SourceType.API:
                seen_sources.add(hint)
                artifacts.append(Artifact(
                    type="prompt_source",
                    reference=hint,
                    used_by=[obs.id],
                ))
    
    # Build output
    repo_metadata = build_repo_metadata(root, file_index)
    
    output = DiscoveryOutput(
        version="1.0.0",
        tool_version=__version__,
        repo_commit=_get_repo_commit(root),
        scanned_at=datetime.now(timezone.utc),
        repo=repo_metadata,
        project_signals=project_signals,
        nodes=observations,
        artifacts=artifacts,
    )
    
    # Handle baseline comparison
    diff = None
    if baseline:
        try:
            with open(baseline, "r") as f:
                baseline_data = DiscoveryOutput.model_validate_json(f.read())
                diff = compare_discoveries(output, baseline_data)
                # Store diff on output object for summary (non-persistent)
                output._diff = diff
        except Exception as e:
            if not json_only:
                print_warning(f"COULD NOT LOAD BASELINE: {e}")
    
    # Write output
    with open(out, "w", encoding="utf-8") as f:
        f.write(_format_discovery_output(output))
    
    # Print summary
    if not json_only:
        console.print()
        _print_discovery_summary(output)
        
        print_complete({
            "AI OPERATIONS": len(observations),
            "FILES SCANNED": len(file_index.files),
            "OUTPUT": str(out),
        })
        
        console.print()
        
        if observations:
            print_info("NEXT: Review discovery.json and run [flightline learn] on input sources")
    else:
        # Just output JSON
        print(_format_discovery_output(output))


# Context manager for conditional spinner
class nullcontext:
    """Null context manager for Python < 3.10 compatibility."""
    
    def __enter__(self):
        return None
    
    def __exit__(self, *args):
        pass

