#!/usr/bin/env python3
"""Generate the parallel Health Dashboard v5 preview."""
from __future__ import annotations

import argparse
import os
import re
import secrets
import sqlite3
import stat
import sys
import tempfile
from datetime import date, datetime
from pathlib import Path
from zoneinfo import ZoneInfo

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))

from dashboard_v5.data_provider import build_bundle, connect  # noqa: E402
from dashboard_v5.render import render  # noqa: E402

BASE = Path.home() / ".hermes" / "assets" / "Gesundheit"
DEFAULT_OUTPUT = BASE / "reports" / "health_dashboard_v5.html"
LOCAL_TIMEZONE = ZoneInfo("Europe/Zurich")
SYNTHETIC_FIXTURE_MARKER = "dashboard-v5-synthetic-fixture-v1"
PROTOTYPE_ROOT = Path("/tmp")


def local_today() -> date:
    return datetime.now(LOCAL_TIMEZONE).date()


def _inspect_private_prototype_path(
    path: Path, *, label: str, must_exist: bool
) -> tuple[Path, os.stat_result]:
    candidate = path.expanduser()
    if not candidate.is_absolute():
        candidate = Path.cwd() / candidate
    parent_input = candidate.parent
    if parent_input.is_symlink():
        raise ValueError(f"ECharts prototype {label} parent must not be a symlink")
    try:
        parent = parent_input.resolve(strict=True)
    except FileNotFoundError as error:
        raise ValueError(f"ECharts prototype {label} parent must already exist") from error
    if Path(os.path.abspath(parent_input)) != parent:
        raise ValueError(f"ECharts prototype {label} path must not contain symlinks")
    if parent == PROTOTYPE_ROOT or not parent.is_relative_to(PROTOTYPE_ROOT):
        raise ValueError(f"ECharts prototype {label} must stay below /tmp")
    parent_stat = os.stat(parent, follow_symlinks=False)
    if not stat.S_ISDIR(parent_stat.st_mode):
        raise ValueError(f"ECharts prototype {label} parent must be a directory")
    if parent_stat.st_uid != os.getuid() or stat.S_IMODE(parent_stat.st_mode) != 0o700:
        raise ValueError(f"ECharts prototype {label} parent must be owner-controlled mode 0700")
    resolved = parent / candidate.name
    if must_exist:
        if candidate.is_symlink():
            raise ValueError(f"ECharts prototype {label} must not be a symlink")
        try:
            item_stat = os.stat(resolved, follow_symlinks=False)
        except FileNotFoundError as error:
            raise ValueError(f"ECharts prototype {label} must exist") from error
        if not stat.S_ISREG(item_stat.st_mode) or item_stat.st_uid != os.getuid():
            raise ValueError(f"ECharts prototype {label} must be an owner-controlled regular file")
    elif candidate.is_symlink() or candidate.exists():
        raise FileExistsError(f"ECharts prototype {label} must not already exist")
    return resolved, parent_stat


def _private_prototype_path(path: Path, *, label: str, must_exist: bool) -> Path:
    return _inspect_private_prototype_path(
        path, label=label, must_exist=must_exist
    )[0]


def require_synthetic_prototype_db(path: Path) -> Path:
    return _private_prototype_path(path, label="database", must_exist=True)


def connect_synthetic_prototype_db(path: Path) -> sqlite3.Connection:
    resolved = require_synthetic_prototype_db(path)
    expected_stat = os.stat(resolved, follow_symlinks=False)
    descriptor = os.open(resolved, os.O_RDONLY | os.O_NOFOLLOW)
    try:
        actual_stat = os.fstat(descriptor)
        if (actual_stat.st_dev, actual_stat.st_ino) != (
            expected_stat.st_dev,
            expected_stat.st_ino,
        ):
            raise ValueError("ECharts prototype database changed after validation")
        connection = sqlite3.connect(
            f"file:/proc/self/fd/{descriptor}?mode=ro&immutable=1",
            uri=True,
        )
    finally:
        os.close(descriptor)
    connection.row_factory = sqlite3.Row
    try:
        row = connection.execute(
            "SELECT marker FROM dashboard_v5_synthetic_fixture LIMIT 1"
        ).fetchone()
    except sqlite3.Error as error:
        connection.close()
        raise ValueError("ECharts prototype requires the synthetic fixture marker") from error
    if row is None or row["marker"] != SYNTHETIC_FIXTURE_MARKER:
        connection.close()
        raise ValueError("ECharts prototype requires the synthetic fixture marker")
    return connection


def require_synthetic_prototype_output(path: Path) -> Path:
    return _private_prototype_path(path, label="output", must_exist=False)


def write_synthetic_prototype_output(path: Path, content: str) -> Path:
    output, expected_directory_stat = _inspect_private_prototype_path(
        path, label="output", must_exist=False
    )
    directory_fd = os.open(
        output.parent,
        os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
    )
    temporary_name = f".{output.name}.{secrets.token_hex(12)}.tmp"
    descriptor: int | None = None
    linked = False
    try:
        directory_stat = os.fstat(directory_fd)
        if (directory_stat.st_dev, directory_stat.st_ino) != (
            expected_directory_stat.st_dev,
            expected_directory_stat.st_ino,
        ):
            raise ValueError("ECharts prototype output parent changed after validation")
        if directory_stat.st_uid != os.getuid() or stat.S_IMODE(directory_stat.st_mode) != 0o700:
            raise ValueError("ECharts prototype output parent changed or is not private")
        descriptor = os.open(
            temporary_name,
            os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
            0o600,
            dir_fd=directory_fd,
        )
        with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
            descriptor = None
            handle.write(content)
            handle.flush()
            os.fsync(handle.fileno())
        os.link(
            temporary_name,
            output.name,
            src_dir_fd=directory_fd,
            dst_dir_fd=directory_fd,
            follow_symlinks=False,
        )
        linked = True
        os.fsync(directory_fd)
    finally:
        if descriptor is not None:
            os.close(descriptor)
        try:
            os.unlink(temporary_name, dir_fd=directory_fd)
        except FileNotFoundError:
            pass
        os.close(directory_fd)
    if not linked:
        raise RuntimeError("ECharts prototype output was not published")
    return output


def generate(
    db: Path,
    output: Path,
    *,
    today: str,
    generated_at: str | None = None,
    echarts_prototype: bool = False,
    explorer_6c: bool = False,
    calendar_day_6d: bool = False,
    health_record_6e: bool = False,
    runtime_commit: str | None = None,
) -> Path:
    if echarts_prototype:
        output = require_synthetic_prototype_output(output)
        connection = connect_synthetic_prototype_db(db)
    else:
        connection = connect(db)
    try:
        bundle = build_bundle(connection, today=today, generated_at=generated_at)
    finally:
        connection.close()
    if runtime_commit is not None and not re.fullmatch(r"[0-9a-f]{40}", runtime_commit):
        raise ValueError("runtime commit must be a full lowercase Git SHA")
    content = render(
        bundle,
        echarts_prototype=echarts_prototype,
        explorer_6c=explorer_6c or calendar_day_6d or health_record_6e,
        calendar_day_6d=calendar_day_6d or health_record_6e,
        health_record_6e=health_record_6e,
        runtime_commit=runtime_commit,
    )
    if echarts_prototype:
        return write_synthetic_prototype_output(output, content)
    output.parent.mkdir(parents=True, exist_ok=True)
    descriptor, temporary = tempfile.mkstemp(prefix=f".{output.name}.", dir=output.parent)
    try:
        with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
            handle.write(content)
            handle.flush()
            os.fsync(handle.fileno())
        os.chmod(temporary, 0o600)
        os.replace(temporary, output)
    finally:
        if os.path.exists(temporary):
            os.unlink(temporary)
    return output


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--db", type=Path, required=True)
    parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
    parser.add_argument("--today", default=local_today().isoformat())
    parser.add_argument("--generated-at", help="fixed ISO timestamp for deterministic builds")
    parser.add_argument(
        "--runtime-commit",
        help="full lowercase Git SHA embedded as sanitized runtime identity",
    )
    parser.add_argument(
        "--echarts-prototype",
        action="store_true",
        help="render the Sprint-6A ECharts prototype; database and output must both stay below /tmp",
    )
    parser.add_argument(
        "--explorer-6c",
        action="store_true",
        help="render the explicitly enabled local Sprint-6C ECharts explorer",
    )
    parser.add_argument(
        "--calendar-day-6d",
        action="store_true",
        help="render the Sprint-6D calendar and central day drill-down; also enables Sprint-6C explorer",
    )
    parser.add_argument(
        "--health-record-6e",
        action="store_true",
        help="render Sprint-6E health record; also enables Sprint-6D calendar and Sprint-6C explorer",
    )
    args = parser.parse_args(argv)
    if args.echarts_prototype:
        result = generate(
            args.db,
            args.output,
            today=args.today,
            generated_at=args.generated_at,
            echarts_prototype=True,
            runtime_commit=args.runtime_commit,
        )
    elif args.health_record_6e:
        result = generate(
            args.db, args.output, today=args.today, generated_at=args.generated_at,
            health_record_6e=True, runtime_commit=args.runtime_commit,
        )
    elif args.calendar_day_6d:
        result = generate(
            args.db, args.output, today=args.today, generated_at=args.generated_at,
            calendar_day_6d=True, runtime_commit=args.runtime_commit,
        )
    elif args.explorer_6c:
        result = generate(
            args.db, args.output, today=args.today, generated_at=args.generated_at,
            explorer_6c=True, runtime_commit=args.runtime_commit,
        )
    else:
        result = generate(
            args.db, args.output, today=args.today,
            generated_at=args.generated_at, runtime_commit=args.runtime_commit,
        )
    print(result)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
