"""Report command: generate compliance reports in terminal, JSON, HTML, or PDF.

HTML reports are self-contained single files with all CSS/JS inlined.
Requires Pro+ tier (community blocked via check_feature("html_report")).
PDF requires Agency tier (``branded_pdf`` feature) and an API key.
"""

from __future__ import annotations

import asyncio
import json
import os
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING, Annotated

import typer

from complyform.cli.exit_codes import EXIT_COMPLIANCE_FAILURE, EXIT_SUCCESS
from complyform.cli.main import AppContext, app

if TYPE_CHECKING:
    from collections.abc import Sequence

    from complyform.core.assessor.control_matcher import Finding
    from complyform.core.cache import ScanCache
    from complyform.core.scanner.state_parser import ResourceRecord

_BF = "Brownfield (Existing Infrastructure)"

_DEFAULT_API_URL = "https://api.complyform.dev"


@app.command(rich_help_panel=_BF)
def report(
    ctx: typer.Context,
    format: Annotated[
        str,
        typer.Option(
            "--format",
            "-f",
            help="Output format: terminal, json, html, pdf",
        ),
    ] = "terminal",
    frameworks: Annotated[
        str | None,
        typer.Option(
            "--frameworks",
            help="Comma-separated framework IDs to include",
        ),
    ] = None,
    severity: Annotated[
        str | None,
        typer.Option(
            "--severity",
            help="Filter by minimum severity: critical, high, medium, low",
        ),
    ] = None,
    output: Annotated[
        str | None,
        typer.Option(
            "--output",
            "-o",
            help="Output directory for HTML/PDF report (default: cwd)",
        ),
    ] = None,
    brand: Annotated[
        Path | None,
        typer.Option(
            "--brand",
            help="Path to brand.yaml for white-label PDF (Agency tier)",
        ),
    ] = None,
    batch: Annotated[
        Path | None,
        typer.Option("--batch", help="Agency tier: batch report from manifest"),
    ] = None,
    brand_dir: Annotated[
        Path | None,
        typer.Option(
            "--brand-dir",
            help="Batch: directory with {label}/brand.yaml per project",
        ),
    ] = None,
) -> None:
    """Generate compliance reports in various formats."""
    app_ctx: AppContext = ctx.obj

    # ── Batch mode ──────────────────────────────────────────────────────
    if batch is not None:
        from complyform.cli.exit_codes import EXIT_PARTIAL_FAILURE
        from complyform.core.auth.license import LicenseManager
        from complyform.core.auth.tier_enforcement import (
            check_feature,
            enforce_with_refresh,
        )
        from complyform.core.batch.executor import run_batch_operation
        from complyform.core.batch.manifest import BatchProject, load_batch_manifest
        from complyform.core.cache import ScanCache

        license_mgr = LicenseManager()
        tier_info = license_mgr.resolve_tier_info()
        enforce_with_refresh(
            license_mgr, tier_info, check_feature, "batch_scan", tier_info
        )

        manifest = load_batch_manifest(batch)
        output_root = Path(output) if output else Path.cwd() / "reports"

        def _per_project(proj: BatchProject) -> dict[str, object]:
            cache = ScanCache()
            cached = (
                cache.read_by_label(proj.label)
                if hasattr(cache, "read_by_label")
                else cache.latest()
            )
            if cached is None:
                return {"status": "skipped", "reason": "no cached assessment"}

            proj_dir = output_root / proj.label
            proj_dir.mkdir(parents=True, exist_ok=True)

            fw_str = ",".join(proj.frameworks) if proj.frameworks else frameworks

            # Brand YAML for PDF
            proj_brand: Path | None = None
            if brand_dir and format == "pdf":
                candidate = brand_dir / proj.label / "brand.yaml"
                if candidate.exists():
                    proj_brand = candidate

            _generate_html_report(
                app_ctx,
                frameworks=fw_str,
                severity=severity,
                output_dir=str(proj_dir),
            )
            return {
                "status": "success",
                "output_dir": str(proj_dir),
                "brand_yaml": str(proj_brand) if proj_brand else None,
            }

        batch_result = run_batch_operation(
            manifest, "report", _per_project, output_root
        )
        exit_code = EXIT_SUCCESS if batch_result.failed == 0 else EXIT_PARTIAL_FAILURE
        raise typer.Exit(code=exit_code)

    if format not in ("terminal", "json", "html", "pdf"):
        from complyform.core.errors import ComplyFormError

        raise ComplyFormError(
            title="Invalid Format",
            message=f"Unknown report format: '{format}'.",
            fix="Use one of: terminal, json, html, pdf",
            error_code=0,
        )

    if format == "html":
        _generate_html_report(
            app_ctx,
            frameworks=frameworks,
            severity=severity,
            output_dir=output,
        )
    elif format == "pdf":
        _generate_pdf_report(
            app_ctx,
            frameworks=frameworks,
            severity=severity,
            output_dir=output,
            brand_path=brand,
        )
    else:
        from complyform.core.errors import ComplyFormError

        raise ComplyFormError(
            title="Not Yet Implemented",
            message=(
                f"The '{format}' report format is not yet available"
                " via the report command."
            ),
            fix="Use `complyform assess` for terminal/json output.",
        )


def _generate_pdf_report(
    app_ctx: AppContext,
    *,
    frameworks: str | None,
    severity: str | None,
    output_dir: str | None,
    brand_path: Path | None,
) -> None:
    """Generate PDF report via the ComplyForm API.

    Renders HTML locally, optionally loads brand config, then sends
    to the API for PDF conversion.
    """
    import base64

    from complyform.core.auth.license import LicenseManager
    from complyform.core.auth.tier_enforcement import (
        check_feature,
        enforce_with_refresh,
    )
    from complyform.core.errors import PDFRequiresAPIError

    # Resolve tier
    license_mgr = LicenseManager()
    tier_info = license_mgr.resolve_tier_info()

    # Tier gate — branded_pdf requires Agency
    tier_info = enforce_with_refresh(
        license_mgr, tier_info, check_feature, "branded_pdf", tier_info
    )

    # Resolve API key
    api_key = _resolve_api_key_for_pdf()
    if not api_key:
        raise PDFRequiresAPIError(
            "PDF generation requires an API key."
            " Set COMPLYFORM_API_KEY or configure via `complyform config`."
        )

    # Render HTML first (reuse the HTML pipeline)
    from complyform.core.cache import ScanCache
    from complyform.core.errors import NoCachedAssessmentError
    from complyform.core.profiles.cross_mappings import _load_index
    from complyform.core.profiles.registry import _load_registry
    from complyform.reporting.html_report import render_html_report
    from complyform.reporting.report_data import build_report_data

    cache = ScanCache()
    result = cache.latest()
    if result is None:
        raise NoCachedAssessmentError("")

    resources, cache_meta = result
    cloud: str = cache_meta.get("cloud", "")
    fw_list: list[str] = (
        [f.strip() for f in frameworks.split(",") if f.strip()] if frameworks else []
    )

    all_findings: list[Finding] = _load_cached_findings(cache)
    if not all_findings:
        all_findings = _run_assessment(resources, cloud, fw_list)

    if fw_list:
        all_findings = [f for f in all_findings if f.framework in fw_list]

    if severity:
        sev_upper = severity.upper()
        sev_order = ["CRITICAL", "HIGH", "MEDIUM", "LOW"]
        if sev_upper in sev_order:
            min_idx = sev_order.index(sev_upper)
            allowed_sevs = set(sev_order[: min_idx + 1])
            all_findings = [f for f in all_findings if f.severity in allowed_sevs]

    if not fw_list:
        seen: list[str] = []
        for f in all_findings:
            if f.framework not in seen:
                seen.append(f.framework)
        fw_list = seen

    try:
        registry = _load_registry()
    except Exception:
        registry = None

    try:
        cross_idx = _load_index()
    except Exception:
        cross_idx = None

    metadata = {
        "cloud": cloud,
        "scan_source": "state",
        "state_hash": cache_meta.get("state_hash"),
        "frameworks": fw_list,
        "generated_at": datetime.now(UTC).isoformat(),
        "resource_count": len(resources),
        "assessment_duration_ms": 0,
        "project_id": None,
    }

    report_data = build_report_data(
        findings=all_findings,
        cross_mapping_index=cross_idx,
        framework_registry=registry,
        tier=tier_info.tier,
        metadata=metadata,
    )

    html = render_html_report(report_data)

    # Load brand config if provided
    brand_dict: dict[str, object] | None = None
    logo_b64: str | None = None

    if brand_path is not None:
        from complyform.reporting.brand_config import load_brand_config

        brand_cfg = load_brand_config(brand_path)
        brand_dict = brand_cfg.model_dump(mode="json")

        if brand_cfg.logo_path is not None and brand_cfg.logo_path.exists():
            logo_bytes = brand_cfg.logo_path.read_bytes()
            logo_b64 = base64.b64encode(logo_bytes).decode("ascii")

    # Request PDF from API
    from complyform.reporting.pdf_client import request_pdf

    api_base_url = _resolve_api_base_url()
    pdf_bytes = asyncio.run(
        request_pdf(
            api_base_url=api_base_url,
            api_key=api_key,
            html_content=html,
            brand=brand_dict,
            logo_base64=logo_b64,
        )
    )

    # Write PDF file
    today = datetime.now(UTC).strftime("%Y-%m-%d")
    fw_slug = "-".join(fw_list) if fw_list else "all"
    filename = f"complyform-report-{today}-{fw_slug}.pdf"

    out_path = Path(output_dir) if output_dir else Path.cwd()
    out_path.mkdir(parents=True, exist_ok=True)
    filepath = out_path / filename
    filepath.write_bytes(pdf_bytes)

    app_ctx.console.print(f"[green]PDF report saved to {filepath}[/green]")

    has_failures = report_data.summary.failed > 0
    raise SystemExit(EXIT_COMPLIANCE_FAILURE if has_failures else EXIT_SUCCESS)


def _resolve_api_key_for_pdf() -> str | None:
    """Resolve API key from environment or config."""
    key = os.environ.get("COMPLYFORM_API_KEY")
    if key:
        return key

    try:
        from complyform.core.config import load_config, resolve_config_path

        config = load_config(resolve_config_path())
        return config.api_key
    except Exception:
        return None


def _resolve_api_base_url() -> str:
    """Resolve API base URL from environment or config."""
    url = os.environ.get("COMPLYFORM_API_URL")
    if url:
        return url

    try:
        from complyform.core.config import load_config, resolve_config_path

        config = load_config(resolve_config_path())
        api_url: str | None = getattr(config, "api_base_url", None)
        if api_url:
            return api_url
    except Exception:
        pass

    return _DEFAULT_API_URL


def _generate_html_report(
    app_ctx: AppContext,
    *,
    frameworks: str | None,
    severity: str | None,
    output_dir: str | None,
) -> None:
    """Generate HTML report from cached assessment."""
    from complyform.core.auth.license import LicenseManager
    from complyform.core.auth.tier_enforcement import check_feature
    from complyform.core.cache import ScanCache
    from complyform.core.errors import NoCachedAssessmentError
    from complyform.core.profiles.cross_mappings import _load_index
    from complyform.core.profiles.registry import _load_registry

    # Resolve tier
    license_mgr = LicenseManager()
    tier_info = license_mgr.resolve_tier_info()

    # Tier gate
    check_feature("html_report", tier_info)

    # Load cached assessment
    cache = ScanCache()
    result = cache.latest()
    if result is None:
        raise NoCachedAssessmentError("")

    resources, cache_meta = result
    cloud: str = cache_meta.get("cloud", "")
    fw_list: list[str] = (
        [f.strip() for f in frameworks.split(",") if f.strip()] if frameworks else []
    )

    # Try to load cached findings first
    all_findings: list[Finding] = _load_cached_findings(cache)

    if not all_findings:
        # Re-run assessment if no cached findings
        all_findings = _run_assessment(resources, cloud, fw_list)

    # Framework filter
    if fw_list:
        all_findings = [f for f in all_findings if f.framework in fw_list]

    # Severity filter
    if severity:
        sev_upper = severity.upper()
        sev_order = ["CRITICAL", "HIGH", "MEDIUM", "LOW"]
        if sev_upper in sev_order:
            min_idx = sev_order.index(sev_upper)
            allowed_sevs = set(sev_order[: min_idx + 1])
            all_findings = [f for f in all_findings if f.severity in allowed_sevs]

    # Determine actual framework list
    if not fw_list:
        seen: list[str] = []
        for f in all_findings:
            if f.framework not in seen:
                seen.append(f.framework)
        fw_list = seen

    # Load registry + cross-mapping index
    try:
        registry = _load_registry()
    except Exception:
        registry = None

    try:
        cross_idx = _load_index()
    except Exception:
        cross_idx = None

    # Build report data
    from complyform.reporting.report_data import build_report_data

    metadata = {
        "cloud": cloud,
        "scan_source": "state",
        "state_hash": cache_meta.get("state_hash"),
        "frameworks": fw_list,
        "generated_at": datetime.now(UTC).isoformat(),
        "resource_count": len(resources),
        "assessment_duration_ms": 0,
        "project_id": None,
    }

    report_data = build_report_data(
        findings=all_findings,
        cross_mapping_index=cross_idx,
        framework_registry=registry,
        tier=tier_info.tier,
        metadata=metadata,
    )

    # Render HTML
    from complyform.reporting.html_report import render_html_report

    html = render_html_report(report_data)

    # Write file
    today = datetime.now(UTC).strftime("%Y-%m-%d")
    fw_slug = "-".join(fw_list) if fw_list else "all"
    filename = f"complyform-report-{today}-{fw_slug}.html"

    out_path = Path(output_dir) if output_dir else Path.cwd()
    out_path.mkdir(parents=True, exist_ok=True)
    filepath = out_path / filename
    filepath.write_text(html, encoding="utf-8")

    app_ctx.console.print(f"[green]Report saved to {filepath}[/green]")

    # Exit code
    has_failures = report_data.summary.failed > 0
    raise SystemExit(EXIT_COMPLIANCE_FAILURE if has_failures else EXIT_SUCCESS)


def _load_cached_findings(cache: ScanCache) -> list[Finding]:
    """Load findings from cache if they exist."""
    from complyform.core.assessor.control_matcher import Finding

    if not cache._cache_dir.exists():
        return []

    best_dir = None
    best_time = ""
    for entry_dir in cache._cache_dir.iterdir():
        if not entry_dir.is_dir():
            continue
        meta_path = entry_dir / "metadata.json"
        if not meta_path.exists():
            continue
        try:
            meta = json.loads(meta_path.read_text(encoding="utf-8"))
            scanned_at: str = meta.get("scanned_at", "")
            if scanned_at > best_time:
                best_time = scanned_at
                best_dir = entry_dir
        except ValueError, KeyError:
            continue

    if best_dir is None:
        return []

    findings_path = best_dir / "findings.json"
    if not findings_path.exists():
        return []

    try:
        raw = json.loads(findings_path.read_text(encoding="utf-8"))
        return [Finding.model_validate(f) for f in raw]
    except Exception:
        return []


def _run_assessment(
    resources: Sequence[ResourceRecord],
    cloud: str,
    fw_list: list[str],
) -> list[Finding]:
    """Run a fresh assessment against the given frameworks."""
    from complyform.core.assessor.control_matcher import ControlMatcher
    from complyform.core.profiles.cross_mappings import get_all as get_all_cross
    from complyform.core.profiles.mapping_loader import load_mappings
    from complyform.core.profiles.models import CrossMappingIndex
    from complyform.core.profiles.registry import list_frameworks

    all_cross = get_all_cross()
    cross_index = CrossMappingIndex(schema_version=1, mappings=all_cross)

    matcher = ControlMatcher()
    all_findings: list[Finding] = []

    # If no frameworks specified, use all available
    target_fws = fw_list
    if not target_fws:
        target_fws = [f.id for f in list_frameworks()]

    for fw_id in target_fws:
        try:
            mapping_file = load_mappings(fw_id, cloud)
        except Exception:
            continue
        findings = matcher.match(resources, mapping_file, cross_index)  # type: ignore[arg-type]
        all_findings.extend(findings)

    return all_findings
