"""Final verdict generation for the seven-day observer."""

from __future__ import annotations

import hashlib
import json
import os
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Final

from pydantic import JsonValue, TypeAdapter

from .nutricoach_v150_observer_contract import WINDOW_END, WINDOW_START
from .nutricoach_v150_observer_runtime import OUTPUT

_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
_MAX_GAP: Final = timedelta(minutes=30)
_START_GRACE: Final = timedelta(minutes=20)


@dataclass(frozen=True, slots=True)
class FinalVerdict:
    passed: bool
    failures: tuple[str, ...]


def final_verdict(
    observations: tuple[tuple[datetime, bool], ...],
) -> FinalVerdict:
    """Evaluate complete observation coverage."""
    if not observations:
        return FinalVerdict(False, ("coverage_missing",))
    ordered = tuple(sorted(observations))
    failures: list[str] = []
    if ordered[0][0] > WINDOW_START + _START_GRACE:
        failures.append("coverage_start")
    if ordered[-1][0] < WINDOW_END:
        failures.append("coverage_end")
    if any(
        current[0] - previous[0] > _MAX_GAP
        for previous, current in zip(ordered, ordered[1:], strict=False)
    ):
        failures.append("coverage_gap")
    if any(not passed for _, passed in ordered):
        failures.append("failed_observation")
    return FinalVerdict(not failures, tuple(failures))


def _verified_rows() -> tuple[dict[str, JsonValue], ...]:
    rows: list[dict[str, JsonValue]] = []
    previous = "0" * 64
    for line in (OUTPUT / "observations.jsonl").read_bytes().splitlines():
        row = _OBJECT.validate_json(line)
        digest = row.pop("row_digest", None)
        payload = json.dumps(row, sort_keys=True, separators=(",", ":")).encode()
        if (
            not isinstance(digest, str)
            or row.get("previous_row_digest") != previous
            or hashlib.sha256(payload).hexdigest() != digest
        ):
            raise ValueError("observer hash chain")
        row["row_digest"] = digest
        rows.append(row)
        previous = digest
    return tuple(rows)


def finalize(now: datetime) -> FinalVerdict | None:
    """Write one immutable final report when the window closes."""
    if now < WINDOW_END:
        return None
    rows = _verified_rows()
    observations: list[tuple[datetime, bool]] = []
    for row in rows:
        raw_time = row.get("observed_at")
        status = row.get("status")
        if not isinstance(raw_time, str) or not isinstance(status, str):
            raise TypeError("observer row")
        observed_at = datetime.fromisoformat(raw_time)
        if WINDOW_START <= observed_at <= now:
            observations.append((observed_at, status == "PASS"))
    verdict = final_verdict(tuple(observations))
    report: dict[str, JsonValue] = {
        "schema": "nutricoach-v15-seven-day-final-v1",
        "window_start": WINDOW_START.isoformat(),
        "window_end": WINDOW_END.isoformat(),
        "finalized_at": now.isoformat(),
        "status": "PASS" if verdict.passed else "FAIL",
        "failures": list(verdict.failures),
        "observation_count": len(observations),
        "receipt_head": rows[-1]["row_digest"] if rows else "0" * 64,
    }
    payload = json.dumps(report, sort_keys=True, separators=(",", ":")) + "\n"
    temporary = OUTPUT / ".final-report.json.tmp"
    descriptor = os.open(
        temporary,
        os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_CLOEXEC,
        0o600,
    )
    try:
        _ = os.write(descriptor, payload.encode())
        os.fsync(descriptor)
    finally:
        os.close(descriptor)
    os.replace(temporary, OUTPUT / "final-report.json")
    return verdict
