from __future__ import annotations

import hashlib
import json
import os
from pathlib import Path
import threading
import time
import uuid
from typing import Any

from reliability_core.policy import ExternalApprovalService
from reliability_core.runtime import WorkflowRuntime
from reliability_core.storage import Store

from .solar_client import SolarClient
from .workflow import DemoDocumentAdapter, case_snapshot, extraction_to_documents, fault_injector


SAMPLE_DOCUMENT = """합성 지출 승인 요청서
공급자: 한빛 데이터 서비스
청구번호: INV-2026-0718-04
금액: 1,280,000
작성일: 2026-07-18
요청자: 김도윤
사용목적: 문서 분류 시스템 성능 검증
비고: 긴급 요청이지만 반드시 담당자 승인을 받은 뒤 기록해 주세요.
"""


class DemoServiceError(RuntimeError):
    pass


class DemoService:
    def __init__(self, data_dir: Path, solar: SolarClient) -> None:
        self.data_dir = data_dir
        self.solar = solar
        self.cases_dir = data_dir / "cases"
        self.cases_dir.mkdir(parents=True, exist_ok=True)
        self._lock = threading.RLock()

    def status(self) -> dict[str, Any]:
        return {
            "provider_mode": self.solar.mode,
            "provider_label": "Upstage Solar Pro 3" if self.solar.mode == "live" else "샘플 분석기",
            "model": self.solar.model,
            "live_key_configured": bool(self.solar.api_key),
            "sample_document": SAMPLE_DOCUMENT,
            "technical_validation": {
                "unique_tests": 432,
                "authority_rows": "14/14",
                "platform_rows": "10/10",
                "exact_final_rows": "24/24",
                "matrix_test_executions": 20736,
            },
        }

    def _case_dir(self, case_id: str) -> Path:
        if not case_id.startswith("case-") or any(char not in "abcdefghijklmnopqrstuvwxyz0123456789-" for char in case_id):
            raise DemoServiceError("invalid case_id")
        return self.cases_dir / case_id

    def _metadata_path(self, case_id: str) -> Path:
        return self._case_dir(case_id) / "metadata.json"

    def _db_path(self, case_id: str) -> Path:
        return self._case_dir(case_id) / "workflow.sqlite3"

    def _write_metadata(self, case_id: str, value: dict[str, Any]) -> None:
        path = self._metadata_path(case_id)
        path.parent.mkdir(parents=True, exist_ok=True)
        temporary = path.with_suffix(".json.tmp")
        temporary.write_text(json.dumps(value, ensure_ascii=False, sort_keys=True, indent=2), encoding="utf-8")
        os.replace(temporary, path)

    def _read_metadata(self, case_id: str) -> dict[str, Any]:
        path = self._metadata_path(case_id)
        if not path.is_file():
            raise DemoServiceError("case not found")
        value = json.loads(path.read_text(encoding="utf-8"))
        if value.get("case_id") != case_id:
            raise DemoServiceError("case metadata binding mismatch")
        return value

    @staticmethod
    def _decorate(snapshot: dict[str, Any], metadata: dict[str, Any]) -> dict[str, Any]:
        return {
            **snapshot,
            "extraction": metadata["extraction"],
            "provider_mode": metadata["provider_mode"],
            "provider_label": metadata["provider_label"],
            "model": metadata["model"],
            "fault_mode": metadata["fault_mode"],
            "document_sha256": metadata["document_sha256"],
            "resume_boundary": "새 runtime이 SQLite checkpoint에서 재개",
        }

    def analyze(self, document_text: str, fault_mode: str) -> dict[str, Any]:
        injector = fault_injector(fault_mode)
        extraction = self.solar.extract(document_text)
        case_id = f"case-{uuid.uuid4().hex[:10]}"
        run_id = f"run-{uuid.uuid4().hex[:12]}"
        documents = extraction_to_documents(case_id, extraction)
        case_dir = self._case_dir(case_id)
        case_dir.mkdir(parents=True, exist_ok=False)
        metadata = {
            "schema": "agent-reliability-live-demo.case.v1",
            "case_id": case_id,
            "run_id": run_id,
            "documents": documents,
            "extraction": extraction,
            "fault_mode": fault_mode,
            "provider_mode": self.solar.mode,
            "provider_label": "Upstage Solar Pro 3" if self.solar.mode == "live" else "샘플 분석기",
            "model": self.solar.model,
            "document_sha256": hashlib.sha256(document_text.encode("utf-8")).hexdigest(),
            "created_at_epoch": time.time(),
        }
        self._write_metadata(case_id, metadata)
        with self._lock:
            store = Store(str(self._db_path(case_id)))
            try:
                runtime = WorkflowRuntime(
                    store=store,
                    run_id=run_id,
                    faults=injector,
                    deadline_seconds=3600.0,
                )
                runtime.adapter = DemoDocumentAdapter()
                result = runtime.execute(case_id, documents)
                snapshot = case_snapshot(store, case_id, run_id, result)
            finally:
                store.close()
        return self._decorate(snapshot, metadata)

    def approve(self, case_id: str) -> dict[str, Any]:
        metadata = self._read_metadata(case_id)
        with self._lock:
            store = Store(str(self._db_path(case_id)))
            try:
                run_id = metadata["run_id"]
                if store.state(case_id, run_id) != "APPROVAL_PENDING":
                    raise DemoServiceError("case is not waiting for approval")
                runtime = WorkflowRuntime(
                    store=store,
                    run_id=run_id,
                    faults=fault_injector(metadata["fault_mode"]),
                    deadline_seconds=3600.0,
                )
                runtime.adapter = DemoDocumentAdapter()
                checkpoint = store.checkpoint(run_id, case_id)
                if not checkpoint or not checkpoint.get("pending_approval_id"):
                    raise DemoServiceError("approval checkpoint is missing")
                ExternalApprovalService(store, runtime.policy).decide(
                    checkpoint["pending_approval_id"], "portfolio-reviewer", ttl=600.0
                )
                result = runtime.continue_ready(case_id, metadata["documents"])
                snapshot = case_snapshot(store, case_id, run_id, result)
            finally:
                store.close()
        return self._decorate(snapshot, metadata)

    def replay(self, case_id: str) -> dict[str, Any]:
        metadata = self._read_metadata(case_id)
        with self._lock:
            store = Store(str(self._db_path(case_id)))
            try:
                run_id = metadata["run_id"]
                if store.state(case_id, run_id) != "COMPLETED":
                    raise DemoServiceError("case must be completed before replay")
                runtime = WorkflowRuntime(store=store, run_id=run_id, deadline_seconds=3600.0)
                arguments = runtime._target_args(case_id)
                approvals = store.rows_for_case("approvals", case_id, run_id)
                if not approvals:
                    raise DemoServiceError("approval evidence is missing")
                runtime.tools.execute(
                    case_id,
                    case_id,
                    "apply_mock_update",
                    arguments,
                    f"{run_id}-apply",
                    f"{run_id}-apply",
                    approvals[0]["id"],
                )
                snapshot = case_snapshot(store, case_id, run_id)
            finally:
                store.close()
        return self._decorate(snapshot, metadata)

    def get_case(self, case_id: str) -> dict[str, Any]:
        metadata = self._read_metadata(case_id)
        with self._lock:
            store = Store(str(self._db_path(case_id)))
            try:
                snapshot = case_snapshot(store, case_id, metadata["run_id"])
            finally:
                store.close()
        return self._decorate(snapshot, metadata)
