"""complyform scan command — ingest Terraform state, output resource inventory."""

from __future__ import annotations

import hashlib
import json
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Any

import typer
from rich.table import Table

from complyform import __version__
from complyform.cli.exit_codes import (
    EXIT_PARTIAL_FAILURE,
    EXIT_RUNTIME_ERROR,
    EXIT_SUCCESS,
)
from complyform.cli.main import AppContext, app
from complyform.core.cache import ScanCache
from complyform.core.errors import (
    BatchFlagConflictError,
    ComplyFormError,
    InvalidCloudError,
    StateDiscoverConflictError,
)
from complyform.core.scanner.state_parser import (
    LARGE_FILE_BYTES,
    ResourceRecord,
    StateMetadata,
    StateParser,
)

if TYPE_CHECKING:
    from complyform.core.batch.manifest import BatchProject

_VALID_CLOUDS = ("gcp", "aws", "azure")
_REMOTE_PREFIXES = (
    "gs://",
    "s3://",
    "azure://",
    "app.terraform.io/",
)

_BF = "Brownfield (Existing Infrastructure)"

_PREFIX_SOURCE_MAP: dict[str, str] = {
    "gs://": "gcs",
    "s3://": "s3",
    "azure://": "azure_blob",
    "app.terraform.io/": "tfc",
}


def _complete_cloud(incomplete: str) -> list[str]:
    return [c for c in _VALID_CLOUDS if c.startswith(incomplete)]


@app.command(rich_help_panel=_BF)
def scan(  # noqa: PLR0913
    ctx: typer.Context,
    source: Annotated[
        str,
        typer.Option(help="Source type: state or api"),
    ] = "state",
    state: Annotated[
        list[str] | None,
        typer.Option(help="Path to .tfstate file(s)"),
    ] = None,
    cloud: Annotated[
        str,
        typer.Option(
            help="Cloud provider: gcp, aws, azure",
            autocompletion=_complete_cloud,
        ),
    ] = "gcp",
    credentials: Annotated[
        str | None,
        typer.Option(help="Path to credential file override"),
    ] = None,
    format: Annotated[
        str,
        typer.Option("--format", help="Output format: terminal, json"),
    ] = "terminal",
    assess: Annotated[
        bool,
        typer.Option("--assess", help="Chain scan + assess"),
    ] = False,
    frameworks: Annotated[
        str | None,
        typer.Option(help="Frameworks for --assess"),
    ] = None,
    severity: Annotated[
        str | None,
        typer.Option(help="Severity filter for --assess"),
    ] = None,
    discover: Annotated[
        str | None,
        typer.Option(help="Directory for recursive state discovery"),
    ] = None,
    discover_pattern: Annotated[
        str,
        typer.Option(help="Glob pattern for --discover"),
    ] = "*.tfstate",
    batch: Annotated[
        str | None,
        typer.Option(help="Batch manifest path (Pro+ tier)"),
    ] = None,
    parallel: Annotated[
        int,
        typer.Option(help="Batch parallel scans (1-5)"),
    ] = 1,
    iam_analysis: Annotated[
        bool,
        typer.Option(
            "--iam-analysis",
            help="Team+: IAM analysis (passed to --assess)",
        ),
    ] = False,
    cross_validate: Annotated[
        bool,
        typer.Option(
            "--cross-validate",
            help="Team+: Cross-validation (passed to --assess)",
        ),
    ] = False,
    estimate_cost: Annotated[
        bool,
        typer.Option(
            "--estimate-cost",
            help="Team+: Cost estimation (passed to --assess)",
        ),
    ] = False,
) -> None:
    """Scan infrastructure state for compliance checks."""
    from complyform.core.auth.license import LicenseManager
    from complyform.core.auth.tier_enforcement import (
        check_feature,
        check_scan_source,
        enforce_with_refresh,
    )

    app_ctx: AppContext = ctx.obj
    console = app_ctx.console

    license_mgr = LicenseManager()
    tier_info = license_mgr.resolve_tier_info()

    if source != "state":
        scan_source_key = "cloud_api" if source == "api" else source
        tier_info = enforce_with_refresh(
            license_mgr,
            tier_info,
            check_scan_source,
            scan_source_key,
            tier_info,
        )

        # Landing zone detection for API scans
        if source == "api":
            from complyform.core.scanner.landing_zone import detect_landing_zone

            landing_zone_ctx = detect_landing_zone(cloud, "", credentials)
            if landing_zone_ctx.detected and not app_ctx.quiet:
                typer.echo(
                    f"Landing zone detected: {landing_zone_ctx.type}",
                    err=True,
                )

        typer.echo(
            "API scanning is not yet available — coming in a future release.",
            err=True,
        )
        raise typer.Exit(code=0)

    if cloud not in _VALID_CLOUDS:
        raise InvalidCloudError(cloud)

    # Flag conflict validation
    if state is not None and discover is not None:
        raise StateDiscoverConflictError()
    if batch is not None and (state is not None or discover is not None):
        raise BatchFlagConflictError()

    # --- Discovery path ---
    if discover is not None:
        _run_discover(
            ctx=ctx,
            discover_dir=discover,
            discover_pattern=discover_pattern,
            cloud=cloud,
            format=format,
            assess=assess,
            frameworks=frameworks,
            severity=severity,
            license_mgr=license_mgr,
            tier_info=tier_info,
            iam_analysis=iam_analysis,
            cross_validate=cross_validate,
            estimate_cost=estimate_cost,
        )
        return

    # --- Batch path ---
    if batch is not None:
        tier_info = enforce_with_refresh(
            license_mgr,
            tier_info,
            check_feature,
            "batch_scan_assess",
            tier_info,
        )
        _run_batch(
            ctx=ctx,
            batch_path=batch,
            parallel=parallel,
            format=format,
            assess=assess,
            frameworks=frameworks,
            severity=severity,
            license_mgr=license_mgr,
            tier_info=tier_info,
        )
        return

    # --- Single or multi-state path ---
    state_paths = state if state else ["./terraform.tfstate"]

    # Check for remote state URIs and fetch them
    resolved_paths: list[str] = []
    for sp in state_paths:
        is_remote = False
        for prefix, source_key in _PREFIX_SOURCE_MAP.items():
            if sp.startswith(prefix):
                is_remote = True
                tier_info = enforce_with_refresh(
                    license_mgr, tier_info, check_scan_source, source_key, tier_info
                )
                # Fetch remote state
                import asyncio
                import tempfile

                from complyform.core.scanner.remote_backends import fetch_remote_state

                if not app_ctx.quiet:
                    typer.echo(
                        f"Fetching remote state from {sp}...",
                        err=True,
                    )
                remote_bytes = asyncio.get_event_loop().run_until_complete(
                    fetch_remote_state(sp, credentials)
                )
                # Write to temp file for parser
                with tempfile.NamedTemporaryFile(
                    suffix=".tfstate", delete=False
                ) as tmp:
                    tmp.write(remote_bytes)
                resolved_paths.append(tmp.name)
                break
        if not is_remote:
            resolved_paths.append(sp)
    state_paths = resolved_paths

    if len(state_paths) > 1:
        # Multi-state scan
        _run_multi_state(
            ctx=ctx,
            state_paths=[Path(sp) for sp in state_paths],
            cloud=cloud,
            format=format,
            assess=assess,
            frameworks=frameworks,
            severity=severity,
            iam_analysis=iam_analysis,
            cross_validate=cross_validate,
            estimate_cost=estimate_cost,
        )
        return

    # Single state scan
    state_path = state_paths[0]
    start_time = time.monotonic()

    file_path = Path(state_path)
    raw_bytes = b""
    if file_path.exists():
        raw_bytes = file_path.read_bytes()

    use_spinner = len(raw_bytes) > LARGE_FILE_BYTES
    parser = StateParser(verbose=app_ctx.verbose)

    if use_spinner and not app_ctx.quiet:
        from complyform.cli.progress import create_status

        with create_status(
            console,
            "Parsing large state file...",
            quiet=app_ctx.quiet,
            no_progress=app_ctx.no_progress,
        ):
            resources, meta = parser.parse(state_path, cloud)
    else:
        resources, meta = parser.parse(state_path, cloud)

    if not raw_bytes:
        raw_bytes = file_path.read_bytes()

    cache = ScanCache()
    cache.write(raw_bytes, state_path, cloud, resources, meta)

    elapsed_ms = int((time.monotonic() - start_time) * 1000)
    full_hash = hashlib.sha256(raw_bytes).hexdigest()
    state_hash = f"sha256:{full_hash}"

    if format == "json":
        _render_json(
            resources,
            meta,
            cloud,
            state_path,
            state_hash,
            elapsed_ms,
        )
    else:
        _render_terminal(
            console,
            resources,
            cloud,
            state_hash,
            app_ctx.quiet,
        )

    if assess:
        from complyform.cli.assess_cmd import _run_assess

        _run_assess(
            ctx=ctx,
            frameworks_str=frameworks or "",
            format=format,
            severity=severity or "all",
            attest=False,
            attest_output="./attestation.json",
            no_upgrade_hints=False,
            iam_analysis=iam_analysis,
            cross_validate=cross_validate,
            estimate_cost=estimate_cost,
        )


# -------------------------------------------------------------------
# Discovery
# -------------------------------------------------------------------


def _run_discover(  # noqa: PLR0913
    *,
    ctx: typer.Context,
    discover_dir: str,
    discover_pattern: str,
    cloud: str,
    format: str,
    assess: bool,
    frameworks: str | None,
    severity: str | None,
    license_mgr: Any,
    tier_info: Any,
    iam_analysis: bool = False,
    cross_validate: bool = False,
    estimate_cost: bool = False,
) -> None:
    """Run discovery-based multi-state scan."""
    from complyform.core.auth.tier_enforcement import (
        check_discover_limit,
        enforce_with_refresh,
    )
    from complyform.core.scanner.discovery import discover_state_files

    base = Path(discover_dir)
    state_paths = discover_state_files(
        directory=base,
        pattern=discover_pattern,
        limit=tier_info.discover_limit or 50,
    )

    enforce_with_refresh(
        license_mgr,
        tier_info,
        check_discover_limit,
        len(state_paths),
        tier_info,
    )

    _run_multi_state(
        ctx=ctx,
        state_paths=state_paths,
        cloud=cloud,
        format=format,
        assess=assess,
        frameworks=frameworks,
        severity=severity,
        base_directory=base,
        iam_analysis=iam_analysis,
        cross_validate=cross_validate,
        estimate_cost=estimate_cost,
    )


# -------------------------------------------------------------------
# Multi-state
# -------------------------------------------------------------------


def _run_multi_state(  # noqa: PLR0913
    *,
    ctx: typer.Context,
    state_paths: list[Path],
    cloud: str,
    format: str,
    assess: bool,
    frameworks: str | None,
    severity: str | None,
    base_directory: Path | None = None,
    iam_analysis: bool = False,
    cross_validate: bool = False,
    estimate_cost: bool = False,
) -> None:
    """Parse multiple state files, merge, cache, and output."""
    from complyform.core.scanner.discovery import derive_source_label
    from complyform.core.scanner.multi_state import (
        compute_multi_state_cache_key,
        merge_state_inventories,
    )

    app_ctx: AppContext = ctx.obj
    parser = StateParser(verbose=app_ctx.verbose)
    start_time = time.monotonic()
    base = base_directory or Path(".")

    inventories: list[tuple[str, list[ResourceRecord], StateMetadata]] = []
    for sp in state_paths:
        label = derive_source_label(sp, base)
        resources, metadata = parser.parse(str(sp), cloud)
        for r in resources:
            r.source_state = label
        inventories.append((label, resources, metadata))

    merged_resources, all_metadata = merge_state_inventories(inventories)

    cache_key = compute_multi_state_cache_key(state_paths)
    cache = ScanCache()
    # Build combined bytes for cache
    combined_bytes = b"|".join(p.read_bytes() for p in sorted(state_paths, key=str))
    cache.write(
        combined_bytes, f"multi:{cache_key}", cloud, merged_resources, all_metadata[0]
    )

    elapsed_ms = int((time.monotonic() - start_time) * 1000)
    state_hash = f"sha256:{hashlib.sha256(combined_bytes).hexdigest()}"

    if format == "json":
        _render_json(
            merged_resources,
            all_metadata[0],
            cloud,
            f"multi-state ({len(state_paths)} files)",
            state_hash,
            elapsed_ms,
        )
    else:
        _render_terminal(
            app_ctx.console,
            merged_resources,
            cloud,
            state_hash,
            app_ctx.quiet,
        )

    if assess:
        from complyform.cli.assess_cmd import _run_assess

        _run_assess(
            ctx=ctx,
            frameworks_str=frameworks or "",
            format=format,
            severity=severity or "all",
            attest=False,
            attest_output="./attestation.json",
            no_upgrade_hints=False,
            iam_analysis=iam_analysis,
            cross_validate=cross_validate,
            estimate_cost=estimate_cost,
        )


# -------------------------------------------------------------------
# Batch
# -------------------------------------------------------------------


def _scan_single_project(
    project: BatchProject,
    verbose: bool,
) -> tuple[str, list[ResourceRecord], StateMetadata]:
    """Scan a single batch project. Returns (label, resources, metadata)."""
    parser = StateParser(verbose=verbose)
    resources, metadata = parser.parse(project.state, project.cloud)
    for r in resources:
        r.source_state = project.label
    return project.label, resources, metadata


def _run_batch(  # noqa: PLR0913
    *,
    ctx: typer.Context,
    batch_path: str,
    parallel: int,
    format: str,
    assess: bool,
    frameworks: str | None,
    severity: str | None,
    license_mgr: Any,
    tier_info: Any,
) -> None:
    """Run batch scan from manifest."""
    from rich.console import Console

    from complyform.core.auth.tier_enforcement import (
        check_batch_limit,
        enforce_with_refresh,
    )
    from complyform.core.batch.manifest import load_batch_manifest

    app_ctx: AppContext = ctx.obj
    manifest = load_batch_manifest(Path(batch_path))

    enforce_with_refresh(
        license_mgr,
        tier_info,
        check_batch_limit,
        len(manifest.projects),
        tier_info,
    )

    max_workers = min(max(parallel, 1), 5)
    results: dict[str, tuple[list[ResourceRecord], StateMetadata]] = {}
    errors: dict[str, str] = {}

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {}
        for project in manifest.projects:
            future = executor.submit(_scan_single_project, project, app_ctx.verbose)
            futures[future] = project.label

        for future in as_completed(futures):
            label = futures[future]
            try:
                _, resources, metadata = future.result()
                results[label] = (resources, metadata)
            except ComplyFormError as e:
                errors[label] = e.message
            except Exception as e:
                errors[label] = str(e)

    # Output summary table
    console = app_ctx.console
    if not app_ctx.quiet and isinstance(console, Console):
        _render_batch_summary(console, results, errors)

    if format == "json":
        _render_batch_json(results, errors, manifest)

    # Cache each successful result
    cache = ScanCache()
    for label, (resources, metadata) in results.items():
        project = next(p for p in manifest.projects if p.label == label)
        raw_bytes = Path(project.state).read_bytes()
        cache.write(raw_bytes, project.state, project.cloud, resources, metadata)

    # Exit code
    if errors and results:
        raise typer.Exit(code=EXIT_PARTIAL_FAILURE)
    if errors and not results:
        raise typer.Exit(code=EXIT_RUNTIME_ERROR)
    raise typer.Exit(code=EXIT_SUCCESS)


def _render_batch_summary(
    console: Any,
    results: dict[str, tuple[list[ResourceRecord], StateMetadata]],
    errors: dict[str, str],
) -> None:
    """Render batch summary table to stderr."""
    table = Table(title="Batch Scan Summary", show_lines=True)
    table.add_column("Project", style="cyan")
    table.add_column("Status")
    table.add_column("Resources", justify="right")
    table.add_column("Error", style="dim")

    all_labels = sorted(set(list(results.keys()) + list(errors.keys())))
    for label in all_labels:
        if label in results:
            resources, _ = results[label]
            table.add_row(
                label,
                "[green]OK[/green]",
                str(len(resources)),
                "",
            )
        else:
            err_msg = errors.get(label, "Unknown error")
            table.add_row(
                label,
                "[red]FAILED[/red]",
                "-",
                err_msg[:60],
            )

    console.print(table)

    ok = len(results)
    failed = len(errors)
    total = ok + failed
    console.print(
        f"\n{ok}/{total} projects succeeded." + (f" {failed} failed." if failed else "")
    )


def _render_batch_json(
    results: dict[str, tuple[list[ResourceRecord], StateMetadata]],
    errors: dict[str, str],
    manifest: Any,
) -> None:
    """Render batch results as JSON to stdout."""
    projects_out: list[dict[str, Any]] = []
    for project in manifest.projects:
        if project.label in results:
            resources, meta = results[project.label]
            projects_out.append(
                {
                    "label": project.label,
                    "status": "ok",
                    "resource_count": len(resources),
                    "resources": [r.model_dump(mode="json") for r in resources],
                }
            )
        else:
            projects_out.append(
                {
                    "label": project.label,
                    "status": "failed",
                    "error": errors.get(project.label, "Unknown error"),
                }
            )

    envelope = {
        "schema_version": 1,
        "tool": "complyform",
        "tool_version": __version__,
        "command": "scan",
        "mode": "batch",
        "timestamp": datetime.now(UTC).isoformat(),
        "projects": projects_out,
    }
    sys.stdout.write(json.dumps(envelope, indent=2) + "\n")


# -------------------------------------------------------------------
# Render helpers
# -------------------------------------------------------------------


def _render_terminal(
    console: object,
    resources: list[ResourceRecord],
    cloud: str,
    state_hash: str,
    quiet: bool,
) -> None:
    """Render resource inventory as a Rich table to stderr."""
    from rich.console import Console

    if quiet or not isinstance(console, Console):
        return

    table = Table(title="Resource Inventory", show_lines=False)
    table.add_column("Type", style="cyan")
    table.add_column("Name", style="green")
    table.add_column("Address", style="white")
    table.add_column("Module", style="dim")

    for r in resources:
        table.add_row(
            r.resource_type,
            r.name,
            r.address,
            r.module or "",
        )

    console.print(table)
    console.print(
        f"\n[bold]{len(resources)}[/bold] {cloud} resources"
        f" | state hash: {state_hash[:23]}...",
    )


def _render_json(
    resources: list[ResourceRecord],
    meta: StateMetadata,
    cloud: str,
    state_path: str,
    state_hash: str,
    elapsed_ms: int,
) -> None:
    """Render JSON envelope to stdout."""
    resource_dicts = [r.model_dump(mode="json") for r in resources]

    envelope = {
        "schema_version": 1,
        "tool": "complyform",
        "tool_version": __version__,
        "command": "scan",
        "timestamp": datetime.now(UTC).isoformat(),
        "cloud": cloud,
        "scan_source": "state",
        "state_hash": state_hash,
        "resources": resource_dicts,
        "metadata": {
            "resource_count": len(resource_dicts),
            "scan_duration_ms": elapsed_ms,
            "state_path": state_path,
            "terraform_version": meta.terraform_version,
            "provider_versions": meta.provider_versions,
            "skipped_providers": {},
        },
    }

    sys.stdout.write(json.dumps(envelope, indent=2) + "\n")
