#!/usr/bin/env python3
"""Bind a postflight VPS manifest to a captured snapshot and report exact deltas."""

from __future__ import annotations

import argparse
import json
import re
import stat
from datetime import UTC, datetime
from pathlib import Path
from typing import Any

try:
    from .capture_vps_canonical_postgres_snapshot import (
        SOURCE_SNAPSHOT_RECEIPT_SCHEMA,
        build_provenance_binding,
    )
    from .private_receipt_io import print_private_receipt_summary, write_private_json
    from .verify_postgres_parity_manifest import (
        REVIEWED_MANIFEST_SQL_SHA256,
        SINGLETON_KINDS,
        canonical_hash,
        file_sha256,
        load_manifest,
    )
except ImportError:  # pragma: no cover - direct script execution
    from capture_vps_canonical_postgres_snapshot import SOURCE_SNAPSHOT_RECEIPT_SCHEMA, build_provenance_binding
    from private_receipt_io import print_private_receipt_summary, write_private_json
    from verify_postgres_parity_manifest import (
        REVIEWED_MANIFEST_SQL_SHA256,
        SINGLETON_KINDS,
        canonical_hash,
        file_sha256,
        load_manifest,
    )

DELTA_SCHEMA = "livingip.vpsCanonicalSnapshotDelta.v1"
SHA256_RE = re.compile(r"[0-9a-f]{64}\Z")


def service_healthy(value: Any) -> bool:
    if not isinstance(value, dict):
        return False
    try:
        main_pid = int(value.get("MainPID") or 0)
        restarts = int(value.get("NRestarts") or 0)
    except (TypeError, ValueError):
        return False
    return (
        value.get("ActiveState") == "active" and value.get("SubState") == "running" and main_pid > 0 and restarts >= 0
    )


def regular_file(path: Path, label: str) -> Path:
    try:
        mode = path.lstat().st_mode
    except OSError as exc:
        raise ValueError(f"{label} is unavailable: {exc}") from exc
    if not stat.S_ISREG(mode) or path.is_symlink():
        raise ValueError(f"{label} must be a regular non-symlink file")
    return path.resolve()


def load_json(path: Path, label: str) -> dict[str, Any]:
    path = regular_file(path, label)
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        raise ValueError(f"{label} is invalid JSON") from exc
    if not isinstance(payload, dict):
        raise ValueError(f"{label} must be a JSON object")
    return payload


def parse_timestamp(value: Any, label: str) -> datetime:
    text = str(value or "")
    if text.endswith("Z"):
        text = text[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(text)
    except ValueError as exc:
        raise ValueError(f"{label} is not an ISO-8601 timestamp") from exc
    if parsed.tzinfo is None:
        raise ValueError(f"{label} must include a timezone")
    return parsed.astimezone(UTC)


def validate_capture(
    receipt_path: Path,
    manifest_path: Path,
    *,
    expected_authorization_ref: str,
    label: str,
) -> tuple[dict[str, Any], dict[str, Any]]:
    receipt = load_json(receipt_path, f"{label} receipt")
    manifest_path = regular_file(manifest_path, f"{label} manifest")
    manifest = load_manifest(manifest_path)
    snapshot = receipt.get("snapshot") or {}
    source = receipt.get("source") or {}
    dump = receipt.get("dump") or {}
    manifest_receipt = receipt.get("manifest") or {}
    source_context = receipt.get("source_context") or {}
    source_service = receipt.get("source_service") or {}
    binding = receipt.get("provenance_binding") or {}
    expected_binding = build_provenance_binding(
        run_id=str(receipt.get("run_id") or ""),
        authorization_ref=str(receipt.get("capture_authorization_ref") or ""),
        source=source,
        snapshot=snapshot,
        dump_sha256=str(dump.get("sha256") or ""),
        manifest_sql_sha256=str(manifest_receipt.get("manifest_sql_sha256") or ""),
        source_manifest_sha256=file_sha256(manifest_path),
        source_context_sha256=str(source_context.get("sha256") or ""),
    )
    total_rows = sum(int(row["row_count"]) for row in manifest["tables"].values())
    checks = {
        "schema": receipt.get("schema") == SOURCE_SNAPSHOT_RECEIPT_SCHEMA,
        "receipt_version": receipt.get("receipt_version") == 2,
        "status": receipt.get("status") == "pass",
        "authorization": receipt.get("capture_authorization_ref") == expected_authorization_ref,
        "snapshot_exported": receipt.get("snapshot_exported") is True,
        "service_unchanged": receipt.get("service_unchanged") is True,
        "source_service_healthy": source_service.get("unchanged") is True
        and service_healthy(source_service.get("before"))
        and source_service.get("before") == source_service.get("after"),
        "source_not_mutated": receipt.get("production_db_mutated") is False,
        "telegram_not_sent": receipt.get("telegram_send_attempted") is False,
        "manifest_hash": manifest_receipt.get("sha256") == file_sha256(manifest_path),
        "manifest_table_count": manifest_receipt.get("table_count") == len(manifest["tables"]),
        "manifest_total_rows": manifest_receipt.get("total_rows") == total_rows,
        "manifest_read_only": manifest["singleton"]["identity"].get("transaction_read_only") == "on",
        "manifest_database": manifest["singleton"]["identity"].get("database") == source.get("database"),
        "binding_payload": binding.get("payload") == expected_binding["payload"],
        "binding_sha256": binding.get("sha256") == expected_binding["sha256"],
        "binding_algorithm": binding.get("algorithm") == "sha256",
        "dump_sha256": bool(SHA256_RE.fullmatch(str(dump.get("sha256") or ""))),
        "manifest_sql_sha256": bool(SHA256_RE.fullmatch(str(manifest_receipt.get("manifest_sql_sha256") or ""))),
        "manifest_sql_reviewed": manifest_receipt.get("manifest_sql_sha256") == REVIEWED_MANIFEST_SQL_SHA256,
        "source_context_sha256": bool(SHA256_RE.fullmatch(str(source_context.get("sha256") or ""))),
        "snapshot_identity": all(
            bool(snapshot.get(field))
            for field in ("captured_at_utc", "exported_snapshot_id", "txid_snapshot", "wal_lsn", "system_identifier")
        ),
    }
    failed = sorted(name for name, passed in checks.items() if not passed)
    if failed:
        raise ValueError(f"{label} capture failed validation: " + ", ".join(failed))
    return receipt, manifest


def manifest_fingerprint(manifest: dict[str, Any]) -> dict[str, Any]:
    tables = {
        name: {"row_count": int(row["row_count"]), "rowset_md5": row.get("rowset_md5")}
        for name, row in sorted(manifest["tables"].items())
    }
    structures = {
        kind: canonical_hash(manifest["singleton"][kind].get("items", []))
        for kind in sorted(SINGLETON_KINDS - {"identity"})
    }
    return {
        "table_count": len(tables),
        "total_rows": sum(row["row_count"] for row in tables.values()),
        "table_fingerprint_sha256": canonical_hash(tables),
        "structure_fingerprint_sha256": canonical_hash(structures),
        "tables": tables,
        "structures": structures,
    }


def verify_delta(
    *,
    baseline_receipt_path: Path,
    baseline_manifest_path: Path,
    baseline_authorization_ref: str,
    current_receipt_path: Path,
    current_manifest_path: Path,
    current_authorization_ref: str,
) -> dict[str, Any]:
    baseline_receipt, baseline_manifest = validate_capture(
        baseline_receipt_path,
        baseline_manifest_path,
        expected_authorization_ref=baseline_authorization_ref,
        label="baseline",
    )
    current_receipt, current_manifest = validate_capture(
        current_receipt_path,
        current_manifest_path,
        expected_authorization_ref=current_authorization_ref,
        label="current",
    )
    baseline_snapshot = baseline_receipt["snapshot"]
    current_snapshot = current_receipt["snapshot"]
    baseline_time = parse_timestamp(baseline_snapshot["captured_at_utc"], "baseline snapshot time")
    current_time = parse_timestamp(current_snapshot["captured_at_utc"], "current snapshot time")
    if current_time <= baseline_time:
        raise ValueError("current source capture must be newer than the restored baseline capture")
    if current_receipt.get("source") != baseline_receipt.get("source"):
        raise ValueError("current source identity does not match the restored baseline source")
    if current_snapshot.get("system_identifier") != baseline_snapshot.get("system_identifier"):
        raise ValueError("current source PostgreSQL system identifier changed")
    baseline_manifest_sql_sha256 = str((baseline_receipt.get("manifest") or {}).get("manifest_sql_sha256") or "")
    current_manifest_sql_sha256 = str((current_receipt.get("manifest") or {}).get("manifest_sql_sha256") or "")
    if baseline_manifest_sql_sha256 != current_manifest_sql_sha256:
        raise ValueError("baseline and current captures used different manifest SQL algorithms")

    baseline = manifest_fingerprint(baseline_manifest)
    current = manifest_fingerprint(current_manifest)
    baseline_tables = baseline.pop("tables")
    current_tables = current.pop("tables")
    baseline_structures = baseline.pop("structures")
    current_structures = current.pop("structures")
    added_tables = sorted(set(current_tables) - set(baseline_tables))
    removed_tables = sorted(set(baseline_tables) - set(current_tables))
    changed_tables = [
        {"table": table, "baseline": baseline_tables[table], "current": current_tables[table]}
        for table in sorted(set(baseline_tables) & set(current_tables))
        if baseline_tables[table] != current_tables[table]
    ]
    changed_structures = [
        {
            "kind": kind,
            "baseline_sha256": baseline_structures.get(kind),
            "current_sha256": current_structures.get(kind),
        }
        for kind in sorted(set(baseline_structures) | set(current_structures))
        if baseline_structures.get(kind) != current_structures.get(kind)
    ]
    delta_detected = bool(added_tables or removed_tables or changed_tables or changed_structures)
    return {
        "artifact": "vps_canonical_snapshot_postflight_delta",
        "schema": DELTA_SCHEMA,
        "generated_at_utc": datetime.now(UTC).isoformat(),
        "status": "pass",
        "baseline": {
            "run_id": baseline_receipt["run_id"],
            "capture_authorization_ref": baseline_authorization_ref,
            "captured_at_utc": baseline_snapshot["captured_at_utc"],
            "capture_receipt_sha256": file_sha256(baseline_receipt_path),
            "manifest_sha256": file_sha256(baseline_manifest_path),
            "manifest_sql_sha256": baseline_manifest_sql_sha256,
            **baseline,
        },
        "current": {
            "run_id": current_receipt["run_id"],
            "capture_authorization_ref": current_authorization_ref,
            "captured_at_utc": current_snapshot["captured_at_utc"],
            "capture_receipt_sha256": file_sha256(current_receipt_path),
            "manifest_sha256": file_sha256(current_manifest_path),
            "manifest_sql_sha256": current_manifest_sql_sha256,
            **current,
        },
        "delta": {
            "delta_detected": delta_detected,
            "added_tables": added_tables,
            "removed_tables": removed_tables,
            "changed_tables": changed_tables,
            "changed_structures": changed_structures,
            "total_row_delta": current["total_rows"] - baseline["total_rows"],
        },
        "strongest_claim_allowed": (
            "restored snapshot still matches the newest postflight VPS capture"
            if not delta_detected
            else "restored snapshot parity is bounded to its capture; post-snapshot VPS deltas are explicit"
        ),
    }


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--baseline-receipt", required=True, type=Path)
    parser.add_argument("--baseline-manifest", required=True, type=Path)
    parser.add_argument("--baseline-authorization-ref", required=True)
    parser.add_argument("--current-receipt", required=True, type=Path)
    parser.add_argument("--current-manifest", required=True, type=Path)
    parser.add_argument("--current-authorization-ref", required=True)
    parser.add_argument("--output", required=True, type=Path)
    args = parser.parse_args()
    try:
        payload = verify_delta(
            baseline_receipt_path=args.baseline_receipt,
            baseline_manifest_path=args.baseline_manifest,
            baseline_authorization_ref=args.baseline_authorization_ref,
            current_receipt_path=args.current_receipt,
            current_manifest_path=args.current_manifest,
            current_authorization_ref=args.current_authorization_ref,
        )
    except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as exc:
        payload = {
            "artifact": "vps_canonical_snapshot_postflight_delta",
            "schema": DELTA_SCHEMA,
            "generated_at_utc": datetime.now(UTC).isoformat(),
            "status": "fail",
            "error": str(exc),
            "strongest_claim_allowed": "post-snapshot VPS delta is not proven",
        }
    write_private_json(args.output, payload)
    print_private_receipt_summary(args.output, payload)
    return 0 if payload.get("status") == "pass" else 1


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