#!/usr/bin/env python3
"""Fail-closed validator for the storage-lifecycle executable oracle."""
from __future__ import annotations

import copy
import hashlib
import json
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path

from jsonschema import Draft202012Validator, FormatChecker
from referencing import Registry, Resource

ROOT = Path(__file__).resolve().parent
CHECKSUM_FILES = ("README.md", "case.schema.json", "cases.json", "manifest.json", "manifest.schema.json", "suite.schema.json", "validate.py")


def load(name: str):
    return json.loads((ROOT / name).read_text(encoding="utf-8"))


def instant(value: str) -> datetime:
    if not isinstance(value, str) or not value.endswith("Z"):
        raise AssertionError(f"non-UTC instant: {value!r}")
    parsed = datetime.fromisoformat(value[:-1] + "+00:00")
    if parsed.tzinfo != timezone.utc:
        raise AssertionError(f"non-UTC instant: {value!r}")
    return parsed


def derive(case: dict) -> None:
    case_id, operation = case["caseId"], case["operation"]
    data, expected = case["input"], case["expected"]
    if operation == "issue_signed_url" and expected["outcome"] == "accepted":
        configured = data["configuredTtlSeconds"]
        if not isinstance(configured, int) or isinstance(configured, bool) or not 30 <= configured <= 300:
            raise AssertionError(f"{case_id}: accepted invalid configured TTL")
        calculated = min(instant(data["now"]) + timedelta(seconds=configured), instant(data["zipExpiresAt"]))
        declared = instant(expected["state"]["effectiveExpiresAt"])
        if declared != calculated:
            raise AssertionError(f"{case_id}: effective expiry is declared {declared.isoformat()}, derived {calculated.isoformat()}")
    if operation == "reconcile_objects" and "sloWithinTwoHours" in expected["state"]:
        lag = instant(data["now"]) - instant(data["deadline"])
        milliseconds = round(lag.total_seconds() * 1000)
        within = lag <= timedelta(hours=2)
        state = expected["state"]
        derived = {
            "lagMilliseconds": milliseconds,
            "sloWithinTwoHours": within,
            "pageAlert": not within,
            "readiness": "READY" if within else "BLOCKED",
        }
        if state != derived:
            raise AssertionError(f"{case_id}: SLO oracle is declared {state!r}, derived {derived!r}")
    if operation == "reconcile_objects" and expected["error"] and expected["error"].get("code") == "DELETION_SLO_BREACH":
        if instant(data["now"]) - instant(data["deadline"]) <= timedelta(hours=2):
            raise AssertionError(f"{case_id}: equality or lower lag misclassified as breach")


def validate_data(cases: list[dict], manifest: dict, suite_schema: dict, manifest_schema: dict, case_schema: dict) -> None:
    registry = Registry().with_resources([
        (case_schema["$id"], Resource.from_contents(case_schema)),
        ("https://pdf2html.invalid/quality/storage-lifecycle/case.schema.json", Resource.from_contents(case_schema)),
    ])
    Draft202012Validator(suite_schema, registry=registry, format_checker=FormatChecker()).validate(cases)
    Draft202012Validator(manifest_schema, format_checker=FormatChecker()).validate(manifest)
    ids = [case["caseId"] for case in cases]
    if len(ids) != len(set(ids)):
        raise AssertionError("duplicate case IDs")
    if manifest["caseCount"] != len(cases) or manifest["orderedCaseIds"] != ids:
        raise AssertionError("manifest count/order does not derive from cases")
    if manifest["firstCaseId"] != ids[0] or manifest["lastCaseId"] != ids[-1]:
        raise AssertionError("manifest first/last IDs do not derive from cases")
    for case in cases:
        derive(case)


def verify_checksums() -> None:
    declared = {}
    for line in (ROOT / "checksums.sha256").read_text(encoding="ascii").splitlines():
        digest, name = line.split("  ", 1)
        declared[name] = digest
    if set(declared) != set(CHECKSUM_FILES):
        raise AssertionError("checksum membership mismatch")
    for name in CHECKSUM_FILES:
        actual = hashlib.sha256((ROOT / name).read_bytes()).hexdigest()
        if declared[name] != actual:
            raise AssertionError(f"checksum mismatch: {name}")


def negative_tests(cases, manifest, suite_schema, manifest_schema, case_schema) -> None:
    shorter = copy.deepcopy(cases)
    next(c for c in shorter if c["caseId"] == "SL-032")["expected"]["state"]["effectiveExpiresAt"] = "2026-01-15T12:00:30.000Z"
    try:
        derive(next(c for c in shorter if c["caseId"] == "SL-032"))
    except Exception:
        pass
    else:
        raise AssertionError("negative test: arbitrary shorter signed TTL was accepted")
    wrong_equality = copy.deepcopy(cases)
    equality = next(c for c in wrong_equality if c["caseId"] == "SL-088")
    equality["expected"].update(outcome="blocked", httpStatus=503, error={"code":"DELETION_SLO_BREACH","message":"Request rejected"})
    equality["expected"]["state"] = {"pageAlert": True, "readiness": "BLOCKED"}
    try:
        derive(equality)
    except Exception:
        pass
    else:
        raise AssertionError("negative test: wrong equality classification was accepted")


def main() -> int:
    cases, manifest = load("cases.json"), load("manifest.json")
    suite_schema, manifest_schema, case_schema = load("suite.schema.json"), load("manifest.schema.json"), load("case.schema.json")
    validate_data(cases, manifest, suite_schema, manifest_schema, case_schema)
    negative_tests(cases, manifest, suite_schema, manifest_schema, case_schema)
    verify_checksums()
    print(f"PASS storage-lifecycle cases={len(cases)} negative_tests=2")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as exc:
        print(f"FAIL {exc}", file=sys.stderr)
        raise SystemExit(1)
