"""
Vizro FinOps Dashboard — CFO-ready interactive cost visualization.

COS1-01: Multi-channel delivery (CLI + Notebook + Dashboard).
Reads from existing CLI JSON exports or runs CLI subprocess.

Usage:
    runbooks finops vizro --data-file artifacts/finops-exports/org-dashboard-*.json
    runbooks finops vizro --profile $AWS_BILLING_PROFILE

Port: 8050 (distinct from JupyterLab 8888, Docusaurus 3000)
Dependency: vizro>=0.1.46 (analytics extras group in pyproject.toml)
"""

import json
import logging
import subprocess
import sys
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

# Category keyword map — mirrors scripts/generate_vizro_dashboard.py taxonomy
_CATEGORIES: dict[str, list[str]] = {
    "Compute": ["EC2", "Lambda", "ECS", "EKS", "Fargate", "Lightsail"],
    "Storage": ["S3", "EBS", "EFS", "Glacier", "Backup"],
    "Database": ["RDS", "DynamoDB", "ElastiCache", "Redshift", "Aurora"],
    "Network": ["CloudFront", "VPC", "Route 53", "Direct Connect", "Transit Gateway", "NAT Gateway"],
    "Security": ["WAF", "Shield", "GuardDuty", "Security Hub", "KMS", "Secrets Manager"],
    "Operations": ["CloudWatch", "CloudTrail", "Config", "Systems Manager"],
}


def load_data(data_file: Optional[str] = None, profile: Optional[str] = None) -> dict:
    """Load cost data from JSON file or run CLI subprocess.

    Priority: data_file > profile > latest file in artifacts/finops-exports/

    Args:
        data_file: Path to a pre-exported org-dashboard-*.json file.
        profile: AWS billing profile name for live CLI subprocess.

    Returns:
        Parsed JSON dict matching the org_cost_dashboard export schema.

    Raises:
        FileNotFoundError: When no data source is available.
    """
    if data_file:
        logger.info("Loading data from %s", data_file)
        with open(data_file) as f:
            return json.load(f)

    if profile:
        import tempfile

        output = Path(tempfile.mkdtemp()) / "vizro-data.json"
        result = subprocess.run(
            [
                "runbooks",
                "finops",
                "dashboard",
                "--profile",
                profile,
                "--export",
                "json",
                "--output-file",
                str(output),
                "--timeframe",
                "monthly",
            ],
            capture_output=True,
            text=True,
            timeout=60,
        )
        if result.returncode == 0 and output.exists():
            logger.info("CLI export succeeded: %s", output)
            with open(output) as f:
                return json.load(f)
        logger.error("CLI subprocess failed (rc=%d): %s", result.returncode, result.stderr[:200])

    # Fallback: latest file in artifacts/finops-exports/
    artifacts_dir = Path("artifacts/finops-exports")
    if artifacts_dir.exists():
        candidates = sorted(
            artifacts_dir.glob("org-dashboard-*.json"),
            key=lambda p: p.stat().st_mtime,
            reverse=True,
        )
        if candidates:
            logger.info("Fallback: using %s", candidates[0])
            with open(candidates[0]) as f:
                return json.load(f)

    raise FileNotFoundError(
        "No data source available. Provide --data-file or --profile, "
        "or ensure artifacts/finops-exports/org-dashboard-*.json exists."
    )


def _categorize_service(service_name: str) -> str:
    """Map AWS service name to a FinOps category."""
    lower = service_name.lower()
    for category, keywords in _CATEGORIES.items():
        if any(kw.lower() in lower for kw in keywords):
            return category
    if "tax" in lower:
        return "Tax"
    return "Other"


def _build_dataframes(data: dict) -> tuple:
    """Convert raw JSON export to pandas DataFrames for Vizro.

    Args:
        data: Parsed org_cost_dashboard JSON export.

    Returns:
        Tuple of (df_accounts, df_services, data) where data is passed through
        for access to summary scalars (organization_total, account_count, etc.).
    """
    import pandas as pd

    # Accounts — supports both list-of-dicts and per_account_costs dict shapes
    accounts_raw = data.get("accounts", [])
    per_account = data.get("per_account_costs", {})

    if accounts_raw:
        df_accounts = pd.DataFrame(accounts_raw)
        if "name" not in df_accounts.columns and "id" in df_accounts.columns:
            df_accounts["name"] = df_accounts["id"]
    elif per_account:
        df_accounts = pd.DataFrame([{"name": k, "cost": v} for k, v in per_account.items()])
    else:
        df_accounts = pd.DataFrame(columns=["name", "cost"])

    if not df_accounts.empty and "cost" in df_accounts.columns:
        df_accounts = df_accounts.sort_values("cost", ascending=False)

    # Services
    service_costs = data.get("service_costs", {})
    if isinstance(service_costs, dict) and service_costs:
        df_services = pd.DataFrame([{"service": k, "cost": float(v)} for k, v in service_costs.items()]).sort_values(
            "cost", ascending=False
        )
    else:
        df_services = pd.DataFrame(columns=["service", "cost"])

    if not df_services.empty:
        df_services["category"] = df_services["service"].apply(_categorize_service)

    return df_accounts, df_services, data


def launch_dashboard(
    profile: Optional[str] = None,
    data_file: Optional[str] = None,
    port: int = 8050,
    mode: str = "executive",
) -> None:
    """Launch the Vizro FinOps dashboard.

    Builds a 3-page interactive dashboard:
    - Page 1: Executive Overview (treemap + KPI cards)
    - Page 2: Account Analysis (horizontal bar, top 20)
    - Page 3: Service Categories (sunburst)

    Args:
        profile: AWS billing profile for live data via CLI subprocess.
        data_file: Path to pre-exported JSON (faster, no AWS call).
        port: HTTP port for the Dash server. Default 8050.
        mode: Persona hint (executive / architect / sre). Reserved for future tab filtering.

    Raises:
        SystemExit: When vizro is not installed or data load fails.
    """
    try:
        import vizro.models as vm
        from vizro import Vizro
        import plotly.express as px
    except ImportError:
        logger.error("vizro not installed")
        sys.exit("vizro not installed. Run: uv sync --group analytics")

    from runbooks.common.rich_utils import console

    try:
        data = load_data(data_file=data_file, profile=profile)
    except FileNotFoundError as exc:
        sys.exit(str(exc))

    df_accounts, df_services, raw = _build_dataframes(data)

    org_total: float = float(raw.get("organization_total", 0))
    if not org_total and not df_services.empty:
        org_total = float(df_services["cost"].sum())

    prev_total: float = float(raw.get("previous_total", 0))
    account_count: int = int(raw.get("account_count", len(df_accounts)))

    # Register DataFrames with Vizro data manager
    vm.data_manager["accounts"] = df_accounts
    vm.data_manager["services"] = df_services

    # --- Page 1: Executive Overview ---
    treemap_fig = (
        px.treemap(
            df_services[df_services["cost"] > 0],
            path=["category", "service"],
            values="cost",
            title="Service Cost Breakdown",
            color="cost",
            color_continuous_scale="RdYlGn_r",
        )
        if not df_services.empty
        else px.bar(title="No service data")
    )

    page_executive = vm.Page(
        title="Executive Overview",
        components=[
            vm.Card(text=f"**Total Spend**\n\n${org_total:,.0f}"),
            vm.Card(text=f"**Previous Month**\n\n${prev_total:,.0f}"),
            vm.Card(text=f"**Accounts**\n\n{account_count}"),
            vm.Graph(figure=treemap_fig),
        ],
    )

    # --- Page 2: Account Analysis ---
    top20 = df_accounts.head(20).copy() if not df_accounts.empty else df_accounts
    account_fig = (
        px.bar(
            top20.sort_values("cost", ascending=True),
            x="cost",
            y="name",
            orientation="h",
            title=f"Top 20 Accounts — ${org_total:,.0f} Total",
            text=top20.sort_values("cost", ascending=True)["cost"].apply(lambda x: f"${x:,.0f}"),
        )
        if not df_accounts.empty
        else px.bar(title="No account data")
    )

    page_accounts = vm.Page(
        title="Account Analysis",
        components=[vm.Graph(figure=account_fig)],
    )

    # --- Page 3: Service Categories ---
    sunburst_fig = (
        px.sunburst(
            df_services[df_services["cost"] > 0],
            path=["category", "service"],
            values="cost",
            title="Cost by Category → Service",
        )
        if not df_services.empty
        else px.bar(title="No service data")
    )

    page_categories = vm.Page(
        title="Service Categories",
        components=[vm.Graph(figure=sunburst_fig)],
    )

    dashboard = vm.Dashboard(
        title=f"FinOps Cost Dashboard — ${org_total:,.0f}",
        pages=[page_executive, page_accounts, page_categories],
    )

    console.print(f"[green]Starting Vizro dashboard on http://localhost:{port}[/green]")
    console.print(
        f"[dim]Data: {data_file or 'CLI subprocess'} | Accounts: {account_count} | Total: ${org_total:,.0f}[/dim]"
    )

    Vizro().build(dashboard).run(port=port)
