"""Generate seed data for dashboard development and testing.

Creates realistic VM registry entries, job records, and placeholder
screenshots so the dashboard has data to display without live VMs.
"""

from __future__ import annotations

import json
import os
import sqlite3
import struct
import tempfile
import zlib
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import TYPE_CHECKING, Any, TypedDict

if TYPE_CHECKING:
    from collections.abc import Mapping, Sequence


class _SeedJobStep(TypedDict):
    agent_id: str
    action: str
    timestamp: str
    outcome: str | None


class _SeedJobRow(TypedDict):
    job_id: str
    vm_name: str
    scenario_name: str
    created_by: str
    last_agent_id: str
    status: str
    steps: list[_SeedJobStep]
    created_at: str
    updated_at: str


class _SeedVmRow(TypedDict):
    vm_name: str
    uuid: str
    running: bool
    last_refreshed: str


def _atomic_write_json(path: Path, data: Mapping[str, Any] | Sequence[Any]) -> None:
    """Write JSON atomically via temp file + rename."""
    path.parent.mkdir(parents=True, exist_ok=True)

    fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)
        Path(tmp).replace(path)
    except BaseException:
        Path(tmp).unlink(missing_ok=True)
        raise


def _make_placeholder_png(width: int = 640, height: int = 480, label: str = "VM") -> bytes:
    """Create a placeholder PNG with a visible grid pattern and label.

    No Pillow dependency — writes raw PNG bytes.
    """

    def _chunk(chunk_type: bytes, data: bytes) -> bytes:
        return (
            struct.pack(">I", len(data))
            + chunk_type
            + data
            + struct.pack(">I", zlib.crc32(chunk_type + data) & 0xFFFFFFFF)
        )

    # Colours: background #0F172A, grid #1E293B, accent #22C55E
    bg = (0x0F, 0x17, 0x2A)
    grid = (0x1E, 0x29, 0x3B)
    accent = (0x22, 0xC5, 0x5E)

    grid_spacing = 32
    crosshair_arm = 40  # half-length of the centre crosshair
    bracket_long = 20  # long edge of corner brackets
    bracket_short = 3  # short edge of corner brackets

    cx, cy = width // 2, height // 2

    def _is_accent(x: int, y: int) -> bool:
        """Return True if pixel (x, y) should be drawn in the accent colour."""
        dx, dy = abs(x - cx), abs(y - cy)
        # Centre crosshair (vertical + horizontal arms)
        if (dx < 1 and dy < crosshair_arm) or (dy < 1 and dx < crosshair_arm):
            return True
        # Corner brackets: long side along edges, short side perpendicular
        near_left_or_right = x < bracket_long or x >= width - bracket_long
        near_top_or_bottom = y < bracket_long or y >= height - bracket_long
        thin_h = y < bracket_short or y >= height - bracket_short
        thin_v = x < bracket_short or x >= width - bracket_short
        return (near_left_or_right and thin_h) or (near_top_or_bottom and thin_v)

    rows: list[bytes] = []
    for y in range(height):
        row = bytearray(b"\x00")  # PNG filter byte
        for x in range(width):
            if _is_accent(x, y):
                row.extend(accent)
            elif x % grid_spacing == 0 or y % grid_spacing == 0:
                row.extend(grid)
            else:
                row.extend(bg)
        rows.append(bytes(row))

    raw = b"".join(rows)
    compressed = zlib.compress(raw)

    ihdr = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
    png = b"\x89PNG\r\n\x1a\n"
    png += _chunk(b"IHDR", ihdr)
    png += _chunk(b"IDAT", compressed)
    png += _chunk(b"IEND", b"")
    return png


def seed(root: Path | None = None, screenshots_dir: Path | None = None) -> None:
    """Create seed data in the file stores."""
    root = root or Path(".mcp-vm-blackbox")
    screenshots_dir = screenshots_dir or Path("scratch/screenshots")

    now = datetime.now(UTC)

    # --- VM Registry ---
    vms: list[_SeedVmRow] = [
        {
            "vm_name": "win11-dsr-prod",
            "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "running": True,
            "last_refreshed": now.isoformat(),
        },
        {
            "vm_name": "ubuntu-22-staging",
            "uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
            "running": True,
            "last_refreshed": now.isoformat(),
        },
        {
            "vm_name": "macos-14-build",
            "uuid": "c3d4e5f6-a7b8-9012-cdef-123456789012",
            "running": False,
            "last_refreshed": (now - timedelta(hours=2)).isoformat(),
        },
        {
            "vm_name": "win10-legacy-test",
            "uuid": "d4e5f6a7-b8c9-0123-defa-234567890123",
            "running": False,
            "last_refreshed": (now - timedelta(days=1)).isoformat(),
        },
    ]
    _atomic_write_json(root / "registry" / "vms.json", {"vms": vms})

    # --- Jobs ---
    jobs: list[_SeedJobRow] = [
        # Active job on win11
        {
            "job_id": "a1b2c3d4e5f6",
            "vm_name": "win11-dsr-prod",
            "scenario_name": "dsr-install",
            "created_by": "orchestrator:1234",
            "last_agent_id": "vm-pilot:5678",
            "status": "active",
            "steps": [
                {
                    "agent_id": "orchestrator:1234",
                    "action": "configure",
                    "timestamp": (now - timedelta(minutes=45)).isoformat(),
                    "outcome": "Configuration files deployed",
                },
                {
                    "agent_id": "vm-pilot:5678",
                    "action": "install",
                    "timestamp": (now - timedelta(minutes=30)).isoformat(),
                    "outcome": None,
                },
                {
                    "agent_id": "vm-pilot:5678",
                    "action": "click-next-installer-page-3",
                    "timestamp": (now - timedelta(minutes=5)).isoformat(),
                    "outcome": None,
                },
            ],
            "created_at": (now - timedelta(minutes=45)).isoformat(),
            "updated_at": (now - timedelta(minutes=5)).isoformat(),
        },
        # Paused job on ubuntu
        {
            "job_id": "b2c3d4e5f6a7",
            "vm_name": "ubuntu-22-staging",
            "scenario_name": "postgres-setup",
            "created_by": "orchestrator:1234",
            "last_agent_id": "orchestrator:1234",
            "status": "paused",
            "steps": [
                {
                    "agent_id": "orchestrator:1234",
                    "action": "provision",
                    "timestamp": (now - timedelta(hours=1)).isoformat(),
                    "outcome": "VM provisioned with Vagrant",
                },
                {
                    "agent_id": "orchestrator:1234",
                    "action": "configure-postgres",
                    "timestamp": (now - timedelta(minutes=50)).isoformat(),
                    "outcome": "PostgreSQL 16 installed and configured",
                },
            ],
            "created_at": (now - timedelta(hours=1)).isoformat(),
            "updated_at": (now - timedelta(minutes=50)).isoformat(),
        },
        # Completed job on ubuntu
        {
            "job_id": "c3d4e5f6a7b8",
            "vm_name": "ubuntu-22-staging",
            "scenario_name": "nginx-deploy",
            "created_by": "orchestrator:9012",
            "last_agent_id": "vm-pilot:3456",
            "status": "completed",
            "steps": [
                {
                    "agent_id": "orchestrator:9012",
                    "action": "deploy",
                    "timestamp": (now - timedelta(hours=3)).isoformat(),
                    "outcome": "Nginx config deployed",
                },
                {
                    "agent_id": "vm-pilot:3456",
                    "action": "verify",
                    "timestamp": (now - timedelta(hours=2, minutes=45)).isoformat(),
                    "outcome": "All health checks passed",
                },
            ],
            "created_at": (now - timedelta(hours=3)).isoformat(),
            "updated_at": (now - timedelta(hours=2, minutes=45)).isoformat(),
        },
        # Failed job on macos
        {
            "job_id": "d4e5f6a7b8c9",
            "vm_name": "macos-14-build",
            "scenario_name": "xcode-build",
            "created_by": "orchestrator:1234",
            "last_agent_id": "vm-pilot:7890",
            "status": "failed",
            "steps": [
                {
                    "agent_id": "orchestrator:1234",
                    "action": "build",
                    "timestamp": (now - timedelta(hours=4)).isoformat(),
                    "outcome": "Build started",
                },
                {
                    "agent_id": "vm-pilot:7890",
                    "action": "compile",
                    "timestamp": (now - timedelta(hours=3, minutes=30)).isoformat(),
                    "outcome": "ERROR: Code signing failed — provisioning profile expired",
                },
            ],
            "created_at": (now - timedelta(hours=4)).isoformat(),
            "updated_at": (now - timedelta(hours=3, minutes=30)).isoformat(),
        },
    ]

    for job in jobs:
        vm_n = job["vm_name"]
        job_id_str = job["job_id"]
        path = root / "jobs" / vm_n / f"{job_id_str}.json"
        _atomic_write_json(path, job)

    # .gitignore for jobs dir
    gitignore = root / "jobs" / ".gitignore"
    if not gitignore.exists():
        gitignore.parent.mkdir(parents=True, exist_ok=True)
        gitignore.write_text("*\n!.gitignore\n", encoding="utf-8")

    # --- Placeholder screenshots ---
    screenshots_dir.mkdir(parents=True, exist_ok=True)
    for vm in vms:
        vm_label = vm["vm_name"]
        ts = now.strftime("%Y%m%d-%H%M%S")
        png_path = screenshots_dir / f"{vm_label}-{ts}-seed0001.png"
        if not png_path.exists():
            png_path.write_bytes(_make_placeholder_png(640, 480, vm_label))

    print("Seed data created:")
    print(f"  VMs: {len(vms)}")
    print(f"  Jobs: {len(jobs)}")
    print(f"  Screenshots: {len(vms)}")


# Identifiable seed constants — used by both seed() and clean_seed_data()
_SEED_VM_NAMES: tuple[str, ...] = ("win11-dsr-prod", "ubuntu-22-staging", "macos-14-build", "win10-legacy-test")
_SEED_JOB_IDS: tuple[str, ...] = ("a1b2c3d4e5f6", "b2c3d4e5f6a7", "c3d4e5f6a7b8", "d4e5f6a7b8c9")


def clean_seed_data(root: Path | None = None) -> None:
    """Remove seed-created entries from the SQLite coordination database and JSON files.

    Targets only the known seed VM names and seed job IDs written by :func:`seed`.
    Real VMs and jobs are never touched.

    Args:
        root: Base directory for ``.mcp-vm-blackbox/``.  Defaults to the same
            anchor that :func:`seed` and the dashboard ``db.py`` use
            (project root resolved from this file's location).
    """
    if root is None:
        # Mirror the anchor used in dashboard/server/vm_dashboard/db.py:
        # seed.py → vm_dashboard/ → server/ → dashboard/ → project root
        root = Path(__file__).parent.parent.parent.parent / ".mcp-vm-blackbox"

    db_path = root / "coordination.db"

    # --- SQLite cleanup ---
    if db_path.exists():
        conn = sqlite3.connect(db_path)
        placeholders_vms = ",".join("?" * len(_SEED_VM_NAMES))

        # Discover any additional jobs that belong to the seed VMs
        # (guards against jobs inserted between seed() and clean_seed_data() calls)
        vm_job_ids: list[str] = [
            row[0]
            for row in conn.execute(
                f"SELECT job_id FROM jobs WHERE vm_name IN ({placeholders_vms})",  # noqa: S608
                _SEED_VM_NAMES,
            )
        ]
        all_job_ids = list(set(_SEED_JOB_IDS) | set(vm_job_ids))
        placeholders_all_jobs = ",".join("?" * len(all_job_ids))

        r_steps = conn.execute(
            f"DELETE FROM job_steps WHERE job_id IN ({placeholders_all_jobs})",  # noqa: S608
            all_job_ids,
        )
        r_jobs = conn.execute(
            f"DELETE FROM jobs WHERE job_id IN ({placeholders_all_jobs})",  # noqa: S608
            all_job_ids,
        )
        r_vms = conn.execute(
            f"DELETE FROM vm_registry WHERE vm_name IN ({placeholders_vms})",  # noqa: S608
            _SEED_VM_NAMES,
        )
        conn.commit()
        conn.close()
        print(
            f"SQLite: removed {r_vms.rowcount} vm_registry rows, {r_jobs.rowcount} jobs, {r_steps.rowcount} job_steps"
        )
    else:
        print(f"SQLite: {db_path} not found — skipped")

    # --- JSON file cleanup ---
    vms_json = root / "registry" / "vms.json"
    if vms_json.exists():
        data = json.loads(vms_json.read_text(encoding="utf-8"))
        original = data.get("vms", [])
        kept = [v for v in original if v.get("vm_name") not in _SEED_VM_NAMES]
        if len(kept) < len(original):
            _atomic_write_json(vms_json, {"vms": kept})
            print(f"JSON: removed {len(original) - len(kept)} VM entries from {vms_json}")
        else:
            print("JSON: no seed VM entries found in vms.json")
    else:
        print(f"JSON: {vms_json} not found — skipped")

    for vm_name in _SEED_VM_NAMES:
        for job_id in _SEED_JOB_IDS:
            job_file = root / "jobs" / vm_name / f"{job_id}.json"
            if job_file.exists():
                job_file.unlink()
                print(f"JSON: removed {job_file}")

    print("Seed cleanup complete.")


if __name__ == "__main__":
    import sys

    if "--clean" in sys.argv:
        clean_seed_data()
    else:
        seed()
