"""Receipt persistence helpers."""

from __future__ import annotations

import json
import os
import secrets
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, List

from .contracts import PlanV1, ReceiptArtifact, ReceiptV1


def _receipt_chain_state_path(state_dir: Path) -> Path:
    return Path(state_dir) / "receipts" / "_chain_state.json"


def _load_receipt_chain_state(state_dir: Path) -> dict[str, Any]:
    path = _receipt_chain_state_path(state_dir)
    if not path.exists():
        return {"last_chain_index": 0, "last_receipt_hash": None}
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {"last_chain_index": 0, "last_receipt_hash": None}
    if not isinstance(payload, dict):
        return {"last_chain_index": 0, "last_receipt_hash": None}
    return {
        "last_chain_index": int(payload.get("last_chain_index") or 0),
        "last_receipt_hash": payload.get("last_receipt_hash"),
    }


def _save_receipt_chain_state(state_dir: Path, payload: dict[str, Any]) -> None:
    path = _receipt_chain_state_path(state_dir)
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = path.with_suffix(".json.tmp")
    tmp_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
    os.replace(tmp_path, path)


class ReceiptWriter:
    def __init__(self, state_dir: Path):
        self.state_dir = Path(state_dir)

    def write(self, receipt: ReceiptV1, plan: PlanV1) -> Path:
        receipt_dir = self.state_dir / "receipts" / plan.run_id
        receipt_dir.mkdir(parents=True, exist_ok=True)

        receipt_path = receipt_dir / "receipt.json"
        plan_path = receipt_dir / "plan.json"
        logs_path = receipt_dir / "logs.txt"
        if receipt_path.exists():
            raise FileExistsError(f"Receipt already exists for run_id={plan.run_id}")

        artifacts = list(receipt.artifacts)
        artifacts.append(ReceiptArtifact(type="plan", path=str(plan_path)))
        artifacts.append(ReceiptArtifact(type="logs", path=str(logs_path)))
        receipt.artifacts = artifacts

        chain_state = _load_receipt_chain_state(self.state_dir)
        receipt.chain_index = int(chain_state.get("last_chain_index") or 0) + 1
        receipt.prev_receipt_hash = str(chain_state.get("last_receipt_hash") or "").strip() or None
        receipt.receipt_hash = ReceiptV1.compute_receipt_hash(receipt)

        with open(plan_path, "w", encoding="utf-8") as f:
            json.dump(plan.to_dict(), f, indent=2)

        with open(logs_path, "w", encoding="utf-8") as f:
            f.write("")

        with open(receipt_path, "w", encoding="utf-8") as f:
            json.dump(receipt.to_dict(), f, indent=2)

        _save_receipt_chain_state(
            self.state_dir,
            {
                "last_chain_index": receipt.chain_index,
                "last_receipt_hash": receipt.receipt_hash,
                "last_run_id": plan.run_id,
                "updated_at": datetime.now(timezone.utc).isoformat(),
            },
        )
        return receipt_dir


class ConfigReceiptWriter:
    """Persist immutable module configuration receipts."""

    def __init__(self, state_dir: Path):
        self.state_dir = Path(state_dir)

    def write(
        self,
        *,
        module_id: str,
        actor: str,
        old_envelope: dict,
        new_envelope: dict,
        semantic_diff: dict,
        action: str = "save",
    ) -> tuple[str, Path]:
        receipts_dir = self.state_dir / "config_receipts"
        receipts_dir.mkdir(parents=True, exist_ok=True)
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ")
        receipt_id = f"cfg_{timestamp}_{secrets.token_hex(4)}"
        receipt_path = receipts_dir / f"{receipt_id}.json"
        payload = {
            "receipt_id": receipt_id,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "actor": actor,
            "module_id": module_id,
            "action": action,
            "old_revision_hash": old_envelope.get("revision_hash"),
            "new_revision_hash": new_envelope.get("revision_hash"),
            "old_policy_hash": old_envelope.get("policy_hash"),
            "new_policy_hash": new_envelope.get("policy_hash"),
            "semantic_diff": semantic_diff,
        }
        tmp_path = receipt_path.with_suffix(".json.tmp")
        tmp_path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
        os.replace(tmp_path, receipt_path)
        return receipt_id, receipt_path


def load_receipt(state_dir: Path, run_id: str) -> ReceiptV1:
    receipt_path = Path(state_dir) / "receipts" / run_id / "receipt.json"
    with open(receipt_path, encoding="utf-8") as f:
        payload = json.load(f)
    return ReceiptV1.from_dict(payload)


def list_receipts(state_dir: Path) -> List[str]:
    receipts_root = Path(state_dir) / "receipts"
    if not receipts_root.exists():
        return []
    return sorted([
        path.name
        for path in receipts_root.iterdir()
        if path.is_dir()
    ])


def verify_receipt_chain(state_dir: Path) -> dict[str, Any]:
    receipts_root = Path(state_dir) / "receipts"
    if not receipts_root.exists():
        return {"ok": True, "checked": 0, "failures": []}

    rows: list[tuple[int, str, dict[str, Any]]] = []
    for run_dir in receipts_root.iterdir():
        if not run_dir.is_dir():
            continue
        path = run_dir / "receipt.json"
        if not path.exists():
            continue
        try:
            payload = json.loads(path.read_text(encoding="utf-8"))
        except Exception as exc:
            return {
                "ok": False,
                "checked": len(rows),
                "failures": [{"run_id": run_dir.name, "reason": f"invalid_json:{exc}"}],
            }
        if not isinstance(payload, dict):
            continue
        chain_index = int(payload.get("chain_index") or 0)
        if chain_index <= 0:
            continue
        rows.append((chain_index, run_dir.name, payload))

    if not rows:
        return {"ok": True, "checked": 0, "failures": []}

    rows.sort(key=lambda item: item[0])
    failures: list[dict[str, Any]] = []
    previous_hash: str | None = None
    expected_index = 1
    for chain_index, run_id, payload in rows:
        if chain_index != expected_index:
            failures.append(
                {
                    "run_id": run_id,
                    "reason": "chain_index_gap",
                    "expected_chain_index": expected_index,
                    "actual_chain_index": chain_index,
                }
            )
        stored_prev_hash = str(payload.get("prev_receipt_hash") or "").strip() or None
        if stored_prev_hash != previous_hash:
            failures.append(
                {
                    "run_id": run_id,
                    "reason": "prev_receipt_hash_mismatch",
                    "expected_prev_receipt_hash": previous_hash,
                    "actual_prev_receipt_hash": stored_prev_hash,
                }
            )
        expected_hash = ReceiptV1.compute_receipt_hash(payload)
        stored_hash = str(payload.get("receipt_hash") or "").strip() or None
        if stored_hash != expected_hash:
            failures.append(
                {
                    "run_id": run_id,
                    "reason": "receipt_hash_mismatch",
                    "expected_receipt_hash": expected_hash,
                    "actual_receipt_hash": stored_hash,
                }
            )
        previous_hash = expected_hash
        expected_index = chain_index + 1

    return {"ok": not failures, "checked": len(rows), "failures": failures}


__all__ = ["ReceiptWriter", "ConfigReceiptWriter", "load_receipt", "list_receipts", "verify_receipt_chain"]
