#!/usr/bin/env python3
"""Assemble a Working Leo production-apply receipt from retained artifacts.

This is an offline assembler, not an executor. It refuses to emit a production
receipt unless authorization verification is valid and every expected session
artifact exists. It never opens a database connection, never runs SQL, and
never posts to Telegram.
"""

from __future__ import annotations

import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

REPORT_DIR = Path("docs/reports/leo-working-state-20260709")
DEFAULT_RUNBOOK = REPORT_DIR / "working-leo-authorized-apply-runbook-current.json"
DEFAULT_SESSION_PLAN = REPORT_DIR / "working-leo-authorized-apply-session-plan-current.json"
DEFAULT_RECEIPT_TEMPLATE = REPORT_DIR / "working-leo-production-apply-receipt-template-current.json"
DEFAULT_AUTH_VERIFICATION = REPORT_DIR / "working-leo-production-apply-authorization-verification-current.json"
DEFAULT_ACTUAL_COUNTS = REPORT_DIR / "production-apply-session-artifacts" / "actual-after-apply-counts.json"
DEFAULT_OUT = REPORT_DIR / "working-leo-production-apply-receipt-assembly-current.json"
DEFAULT_MARKDOWN_OUT = REPORT_DIR / "working-leo-production-apply-receipt-assembly-current.md"
DEFAULT_RECEIPT_OUT = REPORT_DIR / "working-leo-production-apply-receipt-current.json"


def _load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def _resolve(path: str | Path, *, repo_root: Path) -> Path:
    candidate = Path(path)
    return candidate if candidate.is_absolute() else repo_root / candidate


def _path_exists(path: str, *, repo_root: Path) -> bool:
    return _resolve(path, repo_root=repo_root).exists()


def _missing_paths(paths: list[str], *, repo_root: Path) -> list[str]:
    return [path for path in paths if not _path_exists(path, repo_root=repo_root)]


def _read_service_log(path: str, *, repo_root: Path) -> dict[str, str]:
    result: dict[str, str] = {}
    resolved = _resolve(path, repo_root=repo_root)
    if not resolved.exists():
        return result
    for line in resolved.read_text(encoding="utf-8").splitlines():
        if "=" in line:
            key, value = line.split("=", 1)
            result[key.strip()] = value.strip()
    return result


def _read_cleanup_log(path: str, *, repo_root: Path) -> dict[str, Any]:
    resolved = _resolve(path, repo_root=repo_root)
    if not resolved.exists():
        return {"checks": {"cleanup_ok": False, "leftover_rehearsal_clone_or_staging_databases": []}}
    leftovers = [line.strip() for line in resolved.read_text(encoding="utf-8").splitlines() if line.strip()]
    return {
        "checks": {
            "cleanup_ok": leftovers == [],
            "leftover_rehearsal_clone_or_staging_databases": leftovers,
        }
    }


def _direct_score_path(template: dict[str, Any]) -> str:
    return str(template.get("receipt_template", {}).get("direct_claim_score_path") or "")


def _receipt_paths(session_plan: dict[str, Any], template: dict[str, Any]) -> dict[str, Any]:
    sources = session_plan["receipt_field_sources_after_execution"]
    return {
        "preflight_output_paths": list(sources["preflight_output_paths"]),
        "apply_output_paths": list(sources["apply_output_paths"]),
        "postflight_output_paths": list(sources["postflight_output_paths"]),
        "telegram_or_handler_regression_paths": list(sources["telegram_or_handler_regression_paths"]),
        "direct_claim_score_path": _direct_score_path(template),
        "service_log_path": str(sources["service_readback"]),
        "cleanup_log_path": str(sources["cleanup_readback"]),
        "rollback_packet_paths": list(sources["rollback_packet_paths"]),
    }


def _counts_match(expected: dict[str, Any], actual: dict[str, Any]) -> bool:
    try:
        normalized_expected = {str(key): int(value) for key, value in expected.items()}
        normalized_actual = {str(key): int(value) for key, value in actual.items()}
    except (TypeError, ValueError):
        return False
    return normalized_actual == normalized_expected


def build_assembly(
    *,
    runbook_path: Path = DEFAULT_RUNBOOK,
    session_plan_path: Path = DEFAULT_SESSION_PLAN,
    receipt_template_path: Path = DEFAULT_RECEIPT_TEMPLATE,
    auth_verification_path: Path = DEFAULT_AUTH_VERIFICATION,
    actual_counts_path: Path = DEFAULT_ACTUAL_COUNTS,
    receipt_out: Path = DEFAULT_RECEIPT_OUT,
    repo_root: Path = Path("."),
) -> dict[str, Any]:
    repo_root = repo_root.resolve()
    runbook = _load_json(runbook_path)
    session_plan = _load_json(session_plan_path)
    receipt_template = _load_json(receipt_template_path)
    auth_verification = _load_json(auth_verification_path)
    paths = _receipt_paths(session_plan, receipt_template)
    expected_counts = dict(runbook["expected_after_apply_counts"])
    actual_counts = _load_json(actual_counts_path) if _resolve(actual_counts_path, repo_root=repo_root).exists() else {}
    path_groups = {
        "preflight_output_paths": paths["preflight_output_paths"],
        "apply_output_paths": paths["apply_output_paths"],
        "postflight_output_paths": paths["postflight_output_paths"],
        "telegram_or_handler_regression_paths": paths["telegram_or_handler_regression_paths"],
        "rollback_packet_paths": paths["rollback_packet_paths"],
        "direct_claim_score_path": [paths["direct_claim_score_path"]],
        "service_log_path": [paths["service_log_path"]],
        "cleanup_log_path": [paths["cleanup_log_path"]],
        "actual_counts_path": [str(actual_counts_path)],
    }
    missing_by_group = {
        group: _missing_paths([path for path in group_paths if path], repo_root=repo_root)
        for group, group_paths in path_groups.items()
    }
    checks = {
        "authorization_verified": auth_verification.get("safe_to_enter_apply_window") is True,
        "authorization_not_executed_by_verifier": auth_verification.get("production_apply_executed") is False,
        "all_session_artifacts_exist": all(not missing for missing in missing_by_group.values()),
        "actual_counts_match_expected": _counts_match(expected_counts, actual_counts),
        "receipt_template_not_final_receipt": receipt_template.get("production_apply_executed") is False
        and receipt_template.get("valid_production_receipt") is False,
        "session_plan_not_executed": session_plan.get("production_apply_executed") is False,
    }
    ready = all(checks.values())
    receipt = {}
    if ready:
        receipt = {
            "actual_after_apply_counts": actual_counts,
            "apply_output_paths": paths["apply_output_paths"],
            "cleanup_readback": _read_cleanup_log(paths["cleanup_log_path"], repo_root=repo_root),
            "direct_claim_score_path": paths["direct_claim_score_path"],
            "explicit_authorization_record": {
                "authorized": True,
                "authorized_by": auth_verification.get("operator_identity"),
                "deployed_sha": auth_verification.get("deployed_sha"),
                "authorization_text": auth_verification.get("expected_authorization_text"),
            },
            "expected_after_apply_counts": expected_counts,
            "git_commit_sha": str(auth_verification.get("deployed_sha") or ""),
            "known_residual_risks": list(receipt_template["receipt_template"].get("known_residual_risks", [])),
            "operator_identity": str(auth_verification.get("operator_identity") or ""),
            "postflight_output_paths": paths["postflight_output_paths"],
            "preflight_output_paths": paths["preflight_output_paths"],
            "rollback_packet_paths": paths["rollback_packet_paths"],
            "service_readback": _read_service_log(paths["service_log_path"], repo_root=repo_root),
            "telegram_or_handler_regression_paths": paths["telegram_or_handler_regression_paths"],
        }
    status = "ready_to_emit_receipt" if ready else "not_ready_missing_or_unverified_session_evidence"
    return {
        "generated_at_utc": datetime.now(timezone.utc).isoformat(),
        "mode": "receipt_assembly_offline",
        "status": status,
        "receipt_ready": ready,
        "production_apply_executed": ready,
        "receipt_out": str(receipt_out),
        "checks": checks,
        "missing_paths_by_group": missing_by_group,
        "expected_after_apply_counts": expected_counts,
        "actual_after_apply_counts": actual_counts,
        "assembled_receipt": receipt,
        "claim_ceiling": (
            "This assembler only builds a receipt from retained post-apply artifacts. It does not run SQL, does not "
            "mutate production, and does not prove canonical rows changed unless receipt_ready=true and the output "
            "receipt validates."
        ),
    }


def write_json(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8")


def write_markdown(path: Path, data: dict[str, Any]) -> None:
    lines = [
        "# Working Leo Production Apply Receipt Assembly",
        "",
        f"Generated UTC: `{data['generated_at_utc']}`",
        f"Mode: `{data['mode']}`",
        f"Status: `{data['status']}`",
        f"Receipt ready: `{data['receipt_ready']}`",
        f"Production apply executed according to assembled evidence: `{data['production_apply_executed']}`",
        f"Receipt output: `{data['receipt_out']}`",
        "",
        "## Checks",
        "",
    ]
    for key, value in sorted(data["checks"].items()):
        lines.append(f"- `{key}`: `{value}`")
    lines.extend(["", "## Missing Paths By Group", ""])
    for group, paths in sorted(data["missing_paths_by_group"].items()):
        lines.append(f"- `{group}`: `{len(paths)}` missing")
        for item in paths[:10]:
            lines.append(f"  - `{item}`")
        if len(paths) > 10:
            lines.append(f"  - ... {len(paths) - 10} more")
    lines.extend(["", "## Claim Ceiling", "", data["claim_ceiling"], ""])
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("\n".join(lines), encoding="utf-8")


def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--runbook", type=Path, default=DEFAULT_RUNBOOK)
    parser.add_argument("--session-plan", type=Path, default=DEFAULT_SESSION_PLAN)
    parser.add_argument("--receipt-template", type=Path, default=DEFAULT_RECEIPT_TEMPLATE)
    parser.add_argument("--auth-verification", type=Path, default=DEFAULT_AUTH_VERIFICATION)
    parser.add_argument("--actual-counts", type=Path, default=DEFAULT_ACTUAL_COUNTS)
    parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
    parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT)
    parser.add_argument("--receipt-out", type=Path, default=DEFAULT_RECEIPT_OUT)
    parser.add_argument("--emit-receipt", action="store_true")
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(argv)
    data = build_assembly(
        runbook_path=args.runbook,
        session_plan_path=args.session_plan,
        receipt_template_path=args.receipt_template,
        auth_verification_path=args.auth_verification,
        actual_counts_path=args.actual_counts,
        receipt_out=args.receipt_out,
    )
    write_json(args.out, data)
    write_markdown(args.markdown_out, data)
    if args.emit_receipt and data["receipt_ready"]:
        write_json(args.receipt_out, data["assembled_receipt"])
    print(
        json.dumps(
            {
                "out": str(args.out),
                "markdown_out": str(args.markdown_out),
                "receipt_ready": data["receipt_ready"],
                "status": data["status"],
            },
            indent=2,
            sort_keys=True,
        )
    )
    return 0 if data["receipt_ready"] else 2


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