"""
Screenshot Metabase dashboards for visual verification.

Takes full-page and per-card screenshots of one or more dashboards. Screenshots
are saved with timestamps for audit trail. Optionally compares two dashboards
structurally (card count, layout, chart types, parameters).

Usage:
    uv run scripts/screenshot_dashboards.py                     # all dashboards from config
    uv run scripts/screenshot_dashboards.py --dashboard-id 42   # single dashboard
    uv run scripts/screenshot_dashboards.py --compare 2 15      # compare two dashboards

Environment variables (from .env):
    METABASE_URL       - e.g. https://metabase.example.com
    METABASE_API_KEY   - Metabase API key (for structural verification API calls)
    METABASE_USERNAME  - Metabase login email (for browser session)
    METABASE_PASSWORD  - Metabase login password (for browser session)
"""

from __future__ import annotations

import argparse
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

import requests
from dotenv import load_dotenv
from playwright.sync_api import Page, sync_playwright

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------

PROJECT_ROOT = Path(__file__).resolve().parents[2]
load_dotenv(PROJECT_ROOT / ".env")

METABASE_URL = os.environ.get("METABASE_URL", "").rstrip("/")
METABASE_API_KEY = os.environ.get("METABASE_API_KEY", "")
METABASE_USERNAME = os.environ.get("METABASE_USERNAME", "")
METABASE_PASSWORD = os.environ.get("METABASE_PASSWORD", "")

SCREENSHOTS_DIR = PROJECT_ROOT / "metabase" / "screenshots"
CARDS_DIR = SCREENSHOTS_DIR / "cards"

# Timestamp for this run
RUN_TS = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")

# Optional: load dashboard IDs from a config file (created by create_dashboards.py)
IDS_FILE = PROJECT_ROOT / "metabase" / ".dashboard_ids.json"


def load_dashboard_ids() -> dict:
    """Load dashboard IDs from config file, if it exists."""
    if IDS_FILE.exists():
        return json.loads(IDS_FILE.read_text())
    return {}


# ---------------------------------------------------------------------------
# Session setup
# ---------------------------------------------------------------------------


def get_session_token() -> str:
    """Get a Metabase session token via API login."""
    resp = requests.post(
        f"{METABASE_URL}/api/session",
        json={"username": METABASE_USERNAME, "password": METABASE_PASSWORD},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json()["id"]


# ---------------------------------------------------------------------------
# Screenshot helpers
# ---------------------------------------------------------------------------


def wait_for_dashboard_load(page: Page, timeout_ms: int = 60_000) -> None:
    """Wait for all dashboard cards to finish loading."""
    # Wait for at least one DashCard to appear
    page.wait_for_selector('[data-testid="dashcard"]', timeout=timeout_ms)

    # Wait for loading spinners to disappear
    page.wait_for_function(
        """() => {
            const spinners = document.querySelectorAll(
                '.Loading, .LoadingSpinner, [data-testid="loading-indicator"], [data-testid="loading-spinner"]'
            );
            return spinners.length === 0;
        }""",
        timeout=timeout_ms,
    )

    # Extra settle time for chart animations and data rendering
    page.wait_for_timeout(5000)


def screenshot_dashboard(
    page: Page,
    dashboard_id: int,
    label: str,
    take_card_screenshots: bool = True,
) -> Path:
    """Navigate to dashboard, wait for load, take full-page + per-card screenshots."""
    url = f"{METABASE_URL}/dashboard/{dashboard_id}"
    print(f"\nScreenshotting {label} (ID {dashboard_id})...")
    print(f"  URL: {url}")

    page.goto(url)
    wait_for_dashboard_load(page)

    # Full-page screenshot
    full_path = SCREENSHOTS_DIR / f"{RUN_TS}_{label}_full.png"
    page.screenshot(path=str(full_path), full_page=True)
    print(f"  Full page: {full_path.name}")

    # Per-card screenshots
    if take_card_screenshots:
        cards = page.query_selector_all('[data-testid="dashcard"]')
        print(f"  Found {len(cards)} cards")
        for i, card in enumerate(cards, 1):
            card.scroll_into_view_if_needed()
            page.wait_for_timeout(500)  # settle after scroll
            card_path = CARDS_DIR / f"{RUN_TS}_{label}_card_{i:02d}.png"
            card.screenshot(path=str(card_path))
            print(f"    Card {i:02d}: {card_path.name}")

    return full_path


# ---------------------------------------------------------------------------
# Structural verification (compare two dashboards)
# ---------------------------------------------------------------------------


def verify_structure(dashboard_a_id: int, dashboard_b_id: int) -> None:
    """Compare structural properties of two dashboards via the API."""
    print("\n" + "=" * 60)
    print("STRUCTURAL VERIFICATION")
    print("=" * 60)

    headers = {"x-api-key": METABASE_API_KEY}
    dash_a = requests.get(
        f"{METABASE_URL}/api/dashboard/{dashboard_a_id}",
        headers=headers,
        timeout=15,
    ).json()
    dash_b = requests.get(
        f"{METABASE_URL}/api/dashboard/{dashboard_b_id}",
        headers=headers,
        timeout=15,
    ).json()

    a_cards = [dc for dc in dash_a.get("dashcards", []) if dc.get("card")]
    b_cards = [dc for dc in dash_b.get("dashcards", []) if dc.get("card")]

    checks = []

    # Card count
    a_count = len(a_cards)
    b_count = len(b_cards)
    passed = a_count == b_count
    checks.append(("Same card count", f"A={a_count}, B={b_count}", passed))

    # Layout positions
    a_positions = sorted(
        [(dc["row"], dc["col"], dc["size_x"], dc["size_y"]) for dc in a_cards]
    )
    b_positions = sorted(
        [(dc["row"], dc["col"], dc["size_x"], dc["size_y"]) for dc in b_cards]
    )
    passed = a_positions == b_positions
    checks.append(
        ("Same layout positions", f"{'Match' if passed else 'MISMATCH'}", passed)
    )

    # Chart types
    a_types = sorted([dc["card"].get("display", "?") for dc in a_cards])
    b_types = sorted([dc["card"].get("display", "?") for dc in b_cards])
    passed = a_types == b_types
    checks.append(("Same chart types", f"{'Match' if passed else 'MISMATCH'}", passed))

    # Card titles
    a_titles = sorted([dc["card"].get("name", "") for dc in a_cards])
    b_titles = sorted([dc["card"].get("name", "") for dc in b_cards])
    passed = a_titles == b_titles
    checks.append(("Same card titles", f"{'Match' if passed else 'MISMATCH'}", passed))

    # Parameters
    a_params = [p.get("name") for p in dash_a.get("parameters", [])]
    b_params = [p.get("name") for p in dash_b.get("parameters", [])]
    passed = a_params == b_params
    checks.append(
        ("Same filter parameters", f"A={a_params}, B={b_params}", passed)
    )

    # Print results
    for name, detail, passed in checks:
        status = "PASS" if passed else "FAIL"
        print(f"  [{status}] {name}: {detail}")

    # Save verification report
    report_path = SCREENSHOTS_DIR / f"{RUN_TS}_verification_report.txt"
    with open(report_path, "w") as f:
        f.write(f"Structural Verification Report — {RUN_TS}\n")
        f.write(f"Dashboard A: {dashboard_a_id}\n")
        f.write(f"Dashboard B: {dashboard_b_id}\n\n")
        for name, detail, passed in checks:
            status = "PASS" if passed else "FAIL"
            f.write(f"[{status}] {name}: {detail}\n")

        if a_titles != b_titles:
            f.write("\nTitle differences:\n")
            f.write(f"  Only in A: {set(a_titles) - set(b_titles)}\n")
            f.write(f"  Only in B: {set(b_titles) - set(a_titles)}\n")

    print(f"\n  Report saved: {report_path.name}")


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------


def main() -> None:
    parser = argparse.ArgumentParser(description="Screenshot Metabase dashboards")
    parser.add_argument(
        "--dashboard-id",
        type=int,
        nargs="+",
        help="Dashboard ID(s) to screenshot. If omitted, reads from .dashboard_ids.json.",
    )
    parser.add_argument(
        "--compare",
        type=int,
        nargs=2,
        metavar=("DASHBOARD_A", "DASHBOARD_B"),
        help="Compare two dashboards structurally after screenshotting.",
    )
    parser.add_argument(
        "--no-cards",
        action="store_true",
        help="Skip per-card screenshots (only take full-page).",
    )
    args = parser.parse_args()

    if not METABASE_URL:
        print("ERROR: METABASE_URL must be set in .env", file=sys.stderr)
        sys.exit(1)
    if not METABASE_USERNAME or not METABASE_PASSWORD:
        print(
            "ERROR: METABASE_USERNAME and METABASE_PASSWORD must be set in .env "
            "(needed for browser login)",
            file=sys.stderr,
        )
        sys.exit(1)

    # Determine which dashboards to screenshot
    dashboard_ids: list[tuple[int, str]] = []
    if args.dashboard_id:
        for did in args.dashboard_id:
            dashboard_ids.append((did, f"dashboard_{did}"))
    elif args.compare:
        dashboard_ids.append((args.compare[0], f"dashboard_a_{args.compare[0]}"))
        dashboard_ids.append((args.compare[1], f"dashboard_b_{args.compare[1]}"))
    else:
        ids = load_dashboard_ids()
        for key, did in ids.items():
            if did and isinstance(did, int):
                dashboard_ids.append((did, key))

    if not dashboard_ids:
        print(
            "ERROR: No dashboards to screenshot. Use --dashboard-id or create "
            ".dashboard_ids.json",
            file=sys.stderr,
        )
        sys.exit(1)

    # Create output directories
    SCREENSHOTS_DIR.mkdir(parents=True, exist_ok=True)
    CARDS_DIR.mkdir(parents=True, exist_ok=True)

    print(f"Metabase: {METABASE_URL}")
    print(f"Run timestamp: {RUN_TS}")
    print(f"Screenshots dir: {SCREENSHOTS_DIR}")
    print(f"Dashboards: {[d[0] for d in dashboard_ids]}")
    print()

    # Get session token for browser cookie
    print("Authenticating...")
    session_token = get_session_token()
    print("  Session obtained")

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(viewport={"width": 1920, "height": 1080})

        # Set Metabase session cookie (avoids form-based login)
        domain = METABASE_URL.replace("https://", "").replace("http://", "")
        context.add_cookies(
            [
                {
                    "name": "metabase.DEVICE",
                    "value": session_token,
                    "domain": domain,
                    "path": "/",
                },
                {
                    "name": "metabase.SESSION",
                    "value": session_token,
                    "domain": domain,
                    "path": "/",
                },
            ]
        )

        page = context.new_page()

        for dashboard_id, label in dashboard_ids:
            screenshot_dashboard(
                page,
                dashboard_id,
                label,
                take_card_screenshots=not args.no_cards,
            )

        browser.close()

    # Structural comparison
    if args.compare:
        if METABASE_API_KEY:
            verify_structure(args.compare[0], args.compare[1])
        else:
            print(
                "\nSkipping structural verification (METABASE_API_KEY not set)",
                file=sys.stderr,
            )

    print(f"\nAll screenshots saved in: {SCREENSHOTS_DIR}")
    print(f"Timestamp prefix: {RUN_TS}")


if __name__ == "__main__":
    main()
