from __future__ import annotations

import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch

from demo.service import DemoService, SAMPLE_DOCUMENT
from demo.solar_client import SolarClient, SolarClientError, _json_from_content
from scripts.live_e2e_runner import run_live_validation


class DemoServiceTests(unittest.TestCase):
    def setUp(self) -> None:
        self.temporary = tempfile.TemporaryDirectory()
        self.service = DemoService(Path(self.temporary.name), SolarClient(api_key=None, allow_sample=True))

    def tearDown(self) -> None:
        self.temporary.cleanup()

    def test_sample_analysis_stops_at_external_approval(self) -> None:
        result = self.service.analyze(SAMPLE_DOCUMENT, "none")
        self.assertEqual(result["state"], "APPROVAL_PENDING")
        self.assertEqual(result["business_state"]["update_count"], 0)
        self.assertEqual(result["duplicate_effect_count"], 0)
        self.assertEqual(result["provider_mode"], "sample")

    def test_external_approval_resumes_from_persisted_checkpoint(self) -> None:
        waiting = self.service.analyze(SAMPLE_DOCUMENT, "none")
        result = self.service.approve(waiting["case_id"])
        self.assertEqual(result["state"], "COMPLETED")
        self.assertEqual(result["business_state"]["update_count"], 1)
        self.assertEqual(result["duplicate_effect_count"], 0)
        self.assertIn("checkpoint", result["resume_boundary"].lower())

    def test_transient_failure_retries_then_completes(self) -> None:
        waiting = self.service.analyze(SAMPLE_DOCUMENT, "transient_before_commit")
        result = self.service.approve(waiting["case_id"])
        self.assertEqual(result["state"], "COMPLETED")
        self.assertEqual(result["business_state"]["update_count"], 1)
        self.assertGreaterEqual(result["retry_event_count"], 1)
        self.assertEqual(result["duplicate_effect_count"], 0)

    def test_response_loss_reuses_committed_result(self) -> None:
        waiting = self.service.analyze(SAMPLE_DOCUMENT, "timeout_after_commit")
        result = self.service.approve(waiting["case_id"])
        self.assertEqual(result["state"], "COMPLETED")
        self.assertEqual(result["business_state"]["update_count"], 1)
        self.assertGreaterEqual(result["result_reuse_count"], 1)
        self.assertEqual(result["duplicate_effect_count"], 0)

    def test_replay_is_idempotent(self) -> None:
        waiting = self.service.analyze(SAMPLE_DOCUMENT, "none")
        completed = self.service.approve(waiting["case_id"])
        replayed = self.service.replay(completed["case_id"])
        self.assertEqual(replayed["business_state"]["update_count"], 1)
        self.assertGreaterEqual(replayed["result_reuse_count"], 1)
        self.assertEqual(replayed["duplicate_effect_count"], 0)

    def test_missing_required_field_never_reaches_tool_execution(self) -> None:
        incomplete = SAMPLE_DOCUMENT.replace("요청자: 김도윤\n", "")
        result = self.service.analyze(incomplete, "none")
        self.assertEqual(result["state"], "NEEDS_INFORMATION")
        self.assertEqual(result["effect_count"], 0)


class SolarStructuredOutputTests(unittest.TestCase):
    def test_markdown_wrapped_json_is_validated(self) -> None:
        payload = {
            "vendor": "가상 공급자",
            "invoice_number": "INV-1",
            "amount_krw": 1000,
            "document_date": "2026-07-18",
            "requested_by": "담당자",
            "purpose": "테스트",
            "missing_fields": [],
            "risk_flags": [],
            "summary": "가상 지출 요청",
        }
        result = _json_from_content(f"```json\n{json.dumps(payload, ensure_ascii=False)}\n```")
        self.assertEqual(result["amount_krw"], 1000)

    def test_unknown_provider_field_is_rejected(self) -> None:
        with self.assertRaises(SolarClientError):
            _json_from_content(json.dumps({"unexpected": True}))

    def test_live_client_uses_server_side_bearer_header(self) -> None:
        payload = {
            "vendor": "가상 공급자",
            "invoice_number": "INV-2",
            "amount_krw": 2000,
            "document_date": "2026-07-18",
            "requested_by": "담당자",
            "purpose": "테스트",
            "missing_fields": [],
            "risk_flags": [],
            "summary": "가상 지출 요청",
        }

        class Response:
            def __enter__(self):
                return self

            def __exit__(self, exc_type, exc, traceback):
                return False

            def read(self):
                body = {"choices": [{"message": {"content": json.dumps(payload, ensure_ascii=False)}}]}
                return json.dumps(body, ensure_ascii=False).encode("utf-8")

        captured = {}

        def fake_urlopen(request, timeout):
            captured["authorization"] = request.get_header("Authorization")
            captured["url"] = request.full_url
            return Response()

        client = SolarClient(api_key="test-only-key", allow_sample=False)
        with patch("demo.solar_client.urlopen", fake_urlopen):
            result = client.extract(SAMPLE_DOCUMENT)
        self.assertEqual(result["invoice_number"], "INV-2")
        self.assertEqual(captured["authorization"], "Bearer test-only-key")
        self.assertEqual(captured["url"], "https://api.upstage.ai/v1/chat/completions")

    def test_one_paste_live_runner_creates_sanitized_pass_receipt(self) -> None:
        repository_root = Path(__file__).resolve().parents[1]
        for launcher in (
            repository_root / "START_UPSTAGE_LIVE_DEMO.cmd",
            repository_root.parent / "START_HERE_UPSTAGE_LIVE_DEMO.cmd",
        ):
            remainder = launcher.read_bytes().replace(b"\r\n", b"")
            self.assertNotIn(b"\r", remainder)
            self.assertNotIn(b"\n", remainder)

        payload = {
            "vendor": "한빛 데이터 서비스",
            "invoice_number": "INV-2026-0718-04",
            "amount_krw": 1280000,
            "document_date": "2026-07-18",
            "requested_by": "김도윤",
            "purpose": "문서 분류 시스템 성능 검증",
            "missing_fields": [],
            "risk_flags": ["고액 지출 - 사람 승인 필요"],
            "summary": "합성 지출 요청",
        }

        class Response:
            def __enter__(self):
                return self

            def __exit__(self, exc_type, exc, traceback):
                return False

            def read(self):
                body = {"choices": [{"message": {"content": json.dumps(payload, ensure_ascii=False)}}]}
                return json.dumps(body, ensure_ascii=False).encode("utf-8")

        def fake_urlopen(request, timeout):
            return Response()

        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            receipt_path = root / "LIVE_PROVIDER_E2E_RECEIPT.json"
            client = SolarClient(api_key="test-only-key", allow_sample=False)
            with patch("demo.solar_client.urlopen", fake_urlopen):
                receipt = run_live_validation(
                    solar=client,
                    data_dir=root / "data",
                    receipt_path=receipt_path,
                    open_browser=False,
                    serve_seconds=0,
                    repackage=False,
                )
            text = receipt_path.read_text(encoding="utf-8")
            self.assertEqual(receipt["verdict"], "PASS")
            self.assertEqual(receipt["results"]["update_count_after_replay"], 1)
            self.assertEqual(receipt["results"]["duplicate_effect_count"], 0)
            self.assertNotIn("test-only-key", text)
            self.assertFalse(receipt["api_key_persisted"])


if __name__ == "__main__":
    unittest.main()
