from __future__ import annotations

import argparse
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import threading
import time
from typing import Any
from urllib.parse import quote
from urllib.request import Request, urlopen
import webbrowser


ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from demo.server import build_server  # noqa: E402
from demo.solar_client import SolarClient  # noqa: E402


DEFAULT_RECEIPT = ROOT / "evidence" / "LIVE_PROVIDER_E2E_RECEIPT.json"
VALIDATION_SUMMARY = ROOT / "evidence" / "demo_validation_summary.json"
RELEASE_ARTIFACT_NAMES = (
    "START_HERE_UPSTAGE_LIVE_DEMO.cmd",
    "AGENT_RELIABILITY_UPSTAGE_SUBMISSION_R5_20260718.zip",
    "AGENT_RELIABILITY_UPSTAGE_SUBMISSION_R5_20260718.zip.sha256.json",
    "AGENT_RELIABILITY_PORTFOLIO_KO_R2_20260718.pdf",
)


def request_json(url: str, payload: dict[str, object] | None = None) -> dict[str, Any]:
    body = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8")
    request = Request(
        url,
        data=body,
        headers={"Content-Type": "application/json"} if body is not None else {},
        method="POST" if body is not None else "GET",
    )
    with urlopen(request, timeout=60) as response:
        value = json.loads(response.read().decode("utf-8"))
    if not value.get("ok"):
        raise RuntimeError(str(value.get("error") or "live demo request failed"))
    return value["data"]


def atomic_json(path: Path, value: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    temporary = path.with_suffix(path.suffix + ".tmp")
    temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    os.replace(temporary, path)


def update_validation_summary(receipt: dict[str, Any]) -> None:
    if not VALIDATION_SUMMARY.is_file():
        return
    summary = json.loads(VALIDATION_SUMMARY.read_text(encoding="utf-8"))
    summary["verdict"] = "PASS"
    solar = summary.setdefault("solar_adapter", {})
    solar["live_provider_end_to_end"] = "PASS"
    solar["live_receipt"] = DEFAULT_RECEIPT.name
    solar["validated_at_utc"] = receipt["validated_at_utc"]
    atomic_json(VALIDATION_SUMMARY, summary)


def export_results(export_dir: Path, receipt_path: Path) -> list[Path]:
    export_dir.mkdir(parents=True, exist_ok=True)
    sources = [ROOT.parent / name for name in RELEASE_ARTIFACT_NAMES]
    sources.append(receipt_path)
    missing = [str(path) for path in sources if not path.is_file()]
    if missing:
        raise RuntimeError(f"자동 저장할 결과 파일이 없습니다: {missing}")

    exported: list[Path] = []
    for source in sources:
        destination = export_dir / source.name
        temporary = destination.with_name(destination.name + ".tmp")
        shutil.copy2(source, temporary)
        os.replace(temporary, destination)
        exported.append(destination)
    return exported


def receipt_for(status: dict[str, Any], analyzed: dict[str, Any], completed: dict[str, Any], replayed: dict[str, Any]) -> dict[str, Any]:
    checks = {
        "provider_mode_live": status["provider_mode"] == "live",
        "analysis_stopped_for_human_approval": analyzed["state"] == "APPROVAL_PENDING",
        "no_write_before_approval": (analyzed.get("business_state") or {}).get("update_count", 0) == 0,
        "completed_after_checkpoint_resume": completed["state"] == "COMPLETED",
        "one_business_write": (replayed.get("business_state") or {}).get("update_count") == 1,
        "zero_duplicate_effects": replayed.get("duplicate_effect_count") == 0,
        "committed_result_reused": replayed.get("result_reuse_count", 0) >= 1,
    }
    if not all(checks.values()):
        raise RuntimeError(f"live validation checks failed: {checks}")
    extraction = analyzed["extraction"]
    receipt: dict[str, Any] = {
        "schema": "agent-reliability-live-demo.live-provider-receipt.v1",
        "verdict": "PASS",
        "validated_at_utc": datetime.now(timezone.utc).isoformat(),
        "provider": "Upstage",
        "provider_mode": status["provider_mode"],
        "model": status["model"],
        "api_key_persisted": False,
        "api_key_in_receipt": False,
        "scenario": "timeout_after_commit_then_idempotent_replay",
        "case_id": analyzed["case_id"],
        "document_sha256": analyzed["document_sha256"],
        "extraction": {
            "vendor": extraction["vendor"],
            "invoice_number": extraction["invoice_number"],
            "amount_krw": extraction["amount_krw"],
            "document_date": extraction["document_date"],
            "requested_by": extraction["requested_by"],
            "purpose": extraction["purpose"],
            "missing_fields": extraction["missing_fields"],
            "risk_flags": extraction["risk_flags"],
            "summary": extraction["summary"],
        },
        "results": {
            "analyze_state": analyzed["state"],
            "final_state": completed["state"],
            "update_count_after_replay": replayed["business_state"]["update_count"],
            "duplicate_effect_count": replayed["duplicate_effect_count"],
            "result_reuse_count": replayed["result_reuse_count"],
        },
        "checks": checks,
    }
    canonical = json.dumps(receipt, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
    receipt["receipt_sha256"] = hashlib.sha256(canonical).hexdigest()
    return receipt


def run_live_validation(
    *,
    solar: SolarClient,
    data_dir: Path,
    receipt_path: Path = DEFAULT_RECEIPT,
    open_browser: bool = True,
    serve_seconds: int = 0,
    repackage: bool = False,
    export_dir: Path | None = None,
) -> dict[str, Any]:
    if solar.mode != "live":
        raise RuntimeError("실제 Upstage Secret key가 필요합니다. 샘플 모드는 자동 검증에서 허용되지 않습니다.")
    server = build_server(host="127.0.0.1", port=0, data_dir=data_dir, solar=solar)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    host, port = server.server_address[:2]
    base_url = f"http://{host}:{port}"
    try:
        status = request_json(f"{base_url}/api/status")
        analyzed = request_json(
            f"{base_url}/api/analyze",
            {"document_text": status["sample_document"], "fault_mode": "timeout_after_commit"},
        )
        completed = request_json(f"{base_url}/api/approve", {"case_id": analyzed["case_id"]})
        replayed = request_json(f"{base_url}/api/replay", {"case_id": analyzed["case_id"]})
        receipt = receipt_for(status, analyzed, completed, replayed)
        atomic_json(receipt_path, receipt)
        if receipt_path.resolve() == DEFAULT_RECEIPT.resolve():
            update_validation_summary(receipt)

        if repackage:
            package_script = ROOT / "scripts" / "package_submission.py"
            if (ROOT.parent / "00_READ_ME_FIRST_KO.md").is_file() and package_script.is_file():
                subprocess.run([sys.executable, str(package_script)], cwd=ROOT, check=True)

        exported = export_results(export_dir, receipt_path) if export_dir is not None else []

        result_url = f"{base_url}/?case_id={quote(analyzed['case_id'])}"
        print("\nLIVE_SOLAR_E2E: PASS")
        print(f"Model: {status['model']}")
        print("Business writes: 1")
        print("Duplicate effects: 0")
        print(f"Receipt: {receipt_path}")
        if exported:
            print(f"Downloads saved: {export_dir}")
        print(f"Result: {result_url}\n")
        if open_browser:
            webbrowser.open(result_url, new=1)
        if serve_seconds > 0:
            print(f"결과 화면은 {serve_seconds // 60}분 동안 유지되며 이후 자동 종료됩니다.")
            deadline = time.monotonic() + serve_seconds
            while time.monotonic() < deadline:
                time.sleep(min(1.0, deadline - time.monotonic()))
        return receipt
    finally:
        server.shutdown()
        server.server_close()
        thread.join(timeout=5)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--no-browser", action="store_true")
    parser.add_argument("--serve-seconds", type=int, default=0)
    parser.add_argument("--repackage", action="store_true")
    parser.add_argument("--export-dir", type=Path)
    arguments = parser.parse_args()
    if sys.version_info < (3, 11):
        raise SystemExit("Python 3.11 이상이 필요합니다.")
    solar = SolarClient.from_environment()
    run_live_validation(
        solar=solar,
        data_dir=ROOT / ".demo-data",
        open_browser=not arguments.no_browser,
        serve_seconds=max(0, arguments.serve_seconds),
        repackage=arguments.repackage,
        export_dir=arguments.export_dir,
    )


if __name__ == "__main__":
    main()
