"""
Scan Command - Parse codebase and build dependency graph.
Enhanced with Discovery/Enforcement modes and manager-readable output.
"""

import json
import logging
import sys
from pathlib import Path

import click
from pydantic import BaseModel
from rich.console import Console

from ...analysis.top_findings import TopFindingsExtractor
from ...core.mode import ScanMode, get_mode_manager
from ...core.packs import detect_and_suggest_pack, load_pack
from ...core.stitching import Stitcher
from ...core.storage.sqlite import SQLiteStorage
from ...parsing.engine import ScanConfig, create_default_engine
from ..formatters.scan_summary import ScanSummaryFormatter
from ..renderers import JsonRenderer
from ..utils import SKIP_DIRS, echo_low_node_warning

logger = logging.getLogger(__name__)
console = Console()


class ScanSummaryResponse(BaseModel):
    """Structured response for the scan command."""

    total_files: int
    files_parsed: int
    files_skipped: int
    nodes_found: int
    edges_found: int
    new_links_stitched: int
    output_path: str
    duration_sec: float
    mode: str
    pack: str | None = None


class _null_context:
    """Helper for non-capture mode."""

    def __enter__(self):
        pass

    def __exit__(self, *args):
        pass


@click.command()
@click.argument("directory", default=".", type=click.Path(exists=True))
@click.option("-o", "--output", help="Output file (default: .jnkn/jnkn.db)")
@click.option("-v", "--verbose", is_flag=True, help="Show detailed output")
@click.option("--no-recursive", is_flag=True, help="Don't scan subdirectories")
@click.option("--force", is_flag=True, help="Force full rescan (ignore incremental cache)")
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
@click.option(
    "--mode",
    type=click.Choice(["discovery", "enforcement"]),
    help="Override scan mode (default: auto from config)",
)
@click.option("--pack", "pack_name", help="Use a specific framework pack")
@click.option("--summary-only", is_flag=True, help="Show only summary, not full details")
def scan(
    directory: str,
    output: str,
    verbose: bool,
    no_recursive: bool,
    force: bool,
    as_json: bool,
    mode: str | None,
    pack_name: str | None,
    summary_only: bool,
):
    """
    Scan directory and build dependency graph.

    Uses incremental scanning by default: only changed files are re-parsed.

    \b
    Modes:
      discovery    - Show more connections (lower threshold), great for first scan
      enforcement  - Show validated connections only, respect suppressions

    \b
    Examples:
        jnkn scan                           # Scan current directory
        jnkn scan ./src --mode discovery    # Force discovery mode
        jnkn scan --pack django-aws         # Use Django+AWS framework pack
        jnkn scan --summary-only            # Brief output
    """
    scan_path = Path(directory).absolute()
    renderer = JsonRenderer("scan")

    # 1. Initialize Mode Manager
    mode_manager = get_mode_manager()

    # Override mode if specified
    if mode:
        mode_manager.set_mode(ScanMode(mode))

    current_mode = mode_manager.current_mode
    min_confidence = mode_manager.min_confidence

    # 2. Handle Framework Pack
    active_pack = None
    active_pack_name = None

    if pack_name:
        active_pack = load_pack(pack_name)
        if active_pack:
            active_pack_name = active_pack.name
        else:
            console.print(f"[yellow]Warning: Pack '{pack_name}' not found[/yellow]")
    else:
        # Auto-detect pack
        suggested = detect_and_suggest_pack(scan_path)
        if suggested:
            active_pack = load_pack(suggested)
            if active_pack:
                active_pack_name = active_pack.name
                if not as_json:
                    console.print(f"[dim]Auto-detected framework pack: {active_pack_name}[/dim]")

    # 3. Determine Output Path and Format
    if output:
        output_path = Path(output)
    else:
        output_path = Path(".jnkn/jnkn.db")

    # If explicit JSON output requested via -o file.json, we'll need to export at the end
    export_to_json = output_path.suffix.lower() == ".json"

    # We always use a DB for the scanning process
    if export_to_json:
        # Use a temporary DB or default DB location for the scan
        db_path = output_path.with_suffix(".db")
    else:
        db_path = output_path

    db_path.parent.mkdir(parents=True, exist_ok=True)

    # Capture context for JSON mode
    context_manager = renderer.capture() if as_json else _null_context()

    error_to_report = None
    response_data = None
    findings_summary = None

    with context_manager:
        try:
            if not as_json and not summary_only:
                console.print(f"🔍 Scanning [cyan]{scan_path}[/cyan]")
                console.print(
                    f"   Mode: [yellow]{current_mode.value}[/yellow] "
                    f"(min_confidence: {min_confidence})"
                )
                if db_path.exists() and not force:
                    console.print("   [dim]Using incremental cache[/dim]")

            # 4. Initialize Engine & Storage
            engine = create_default_engine()
            storage = SQLiteStorage(db_path)

            if force:
                storage.clear()

            # 5. Configure Scan
            skip_dirs = SKIP_DIRS.copy()
            config = ScanConfig(
                root_dir=scan_path,
                skip_dirs=skip_dirs,
                incremental=not force,
            )

            # 6. Run Scan
            def progress(path: Path, current: int, total: int):
                if not as_json and verbose and not summary_only:
                    console.print(f"   [{current}/{total}] {path.name}")

            result = engine.scan_and_store(storage, config, progress_callback=progress)

            if result.is_err():
                raise result.unwrap_err().cause or Exception(result.unwrap_err().message)

            stats = result.unwrap()

            if not as_json and verbose and not summary_only:
                if stats.files_scanned > 0:
                    console.print(
                        f"   Parsed {stats.files_scanned} files ({stats.files_unchanged} unchanged)"
                    )
                if stats.files_failed > 0:
                    console.print(f"   [red]❌ Failed: {stats.files_failed} files[/red]")

            # 7. Hydrate Graph for Stitching
            graph = storage.load_graph()

            # 8. Run Stitching (with pack integration)
            stitched_count = 0
            if graph.node_count > 0:
                # FIX: Show stitching message by default (removed 'verbose' constraint)
                # This ensures the user knows cross-domain analysis is happening
                if not as_json and not summary_only:
                    console.print("🧵 Stitching cross-domain dependencies...")

                # Create stitcher with mode-aware confidence
                stitcher = Stitcher()

                if active_pack:
                    stitcher.apply_pack(active_pack)

                # Run the newly implemented stitch method
                stitched_edges = stitcher.stitch(graph)

                # Filter by mode confidence
                filtered_edges = [
                    e for e in stitched_edges if (e.confidence or 0.5) >= min_confidence
                ]

                if filtered_edges:
                    storage.save_edges_batch(filtered_edges)
                    stitched_count = len(filtered_edges)

            # 9. Extract Top Findings
            graph = storage.load_graph()  # Reload with new edges

            if graph.node_count > 0:
                extractor = TopFindingsExtractor(graph)
                findings_summary = extractor.extract()

            # 10. Format Output
            if not as_json:
                formatter = ScanSummaryFormatter(console)
                formatter.format_summary(
                    nodes_found=graph.node_count,
                    edges_found=graph.edge_count,
                    stitched_count=stitched_count,
                    files_parsed=stats.files_scanned + stats.files_unchanged,
                    duration_sec=stats.scan_time_ms / 1000,
                    mode=current_mode,
                    findings_summary=findings_summary,
                    pack_name=active_pack_name,
                )

                # Check for low node count warning
                if graph.node_count < 5:
                    echo_low_node_warning(graph.node_count)

            # Handle JSON export if requested via -o file.json
            if export_to_json:
                with open(output_path, "w") as f:
                    json.dump(graph.to_dict(), f, indent=2)

            # Prepare API Response
            response_data = ScanSummaryResponse(
                total_files=stats.files_scanned + stats.files_unchanged,
                files_parsed=stats.files_scanned,
                files_skipped=stats.files_skipped,
                nodes_found=graph.node_count,
                edges_found=graph.edge_count,
                new_links_stitched=stitched_count,
                output_path=str(output_path),
                duration_sec=round(stats.scan_time_ms / 1000, 2),
                mode=current_mode.value,
                pack=active_pack_name,
            )

            storage.close()

        except Exception as e:
            error_to_report = e

    # Render output
    if as_json:
        if error_to_report:
            renderer.render_error(error_to_report)
            sys.exit(1)
        elif response_data:
            renderer.render_success(response_data)
    else:
        if error_to_report:
            raise error_to_report
