"""
Kiểm thử 2 bản vá sau sự cố 03/10/2026 (AI đọc sai captcha 'MD3F8C' -> Cổng Thuế từ chối):

1. Đồng thuận đa model (Multi-model Consensus) trước khi gửi captcha lên Cổng Thuế.
   - temperature=0 nên gọi lặp 1 model sẽ cho cùng 1 kết quả (kể cả khi sai) -> phải dùng
     các model KHÁC NHAU làm "người đọc độc lập".
   - Chỉ chấp nhận khi >= 2 model khác nhau (theo model_used thực tế) trả về cùng 1 chuỗi.
   - Không đồng thuận -> trả None để luồng gọi đổi ảnh captcha mới (không tốn lượt đăng nhập).

2. Hiển thị giờ Việt Nam (GMT+7) trong email/Telegram cảnh báo và thông báo cooldown,
   vì container Cloud chạy múi giờ UTC khiến email ghi '01:18' trong khi thực tế là 08:18.
"""

from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, patch
import uuid

import pytest

from app.core.ai.domain.models import AiCapability, AiProviderType, AiResponse
from app.core.ai.infrastructure.registry import CanonicalModelRegistry
from app.modules.invoices.infrastructure.adapters.gdt.gdt_captcha import (
    CAPTCHA_READER_MODELS,
    InvoiceGdtCaptchaMixin,
)


# --------------------------------------------------------------------------
# Helpers
# --------------------------------------------------------------------------
class _FakeGateway:
    """Gateway giả lập: mỗi model trả về một kết quả đọc cấu hình sẵn."""

    def __init__(self, answers: dict[str, str | Exception], model_used_override: str | None = None):
        self.answers = answers
        self.model_used_override = model_used_override
        self.calls: list[str] = []

    def analyze_vision_sync(self, req, timeout: float = 60.0) -> AiResponse:
        model_id = req.canonical_model_id
        self.calls.append(model_id)
        ans = self.answers.get(model_id)
        if isinstance(ans, Exception):
            raise ans
        if ans is None:
            raise RuntimeError(f"model {model_id} unavailable")
        return AiResponse(
            content=ans,
            model_used=self.model_used_override or model_id,
            provider_used=AiProviderType.OPENROUTER,
        )


def _install_gateway(monkeypatch, gateway: _FakeGateway) -> None:
    import app.core.ai.infrastructure.gateway as gw_mod

    monkeypatch.setattr(gw_mod, "get_ai_gateway", lambda: gateway)


PRIMARY, SECONDARY, ARBITER = CAPTCHA_READER_MODELS


# --------------------------------------------------------------------------
# 1. MULTI-MODEL CAPTCHA CONSENSUS
# --------------------------------------------------------------------------
class TestCaptchaConsensus:
    def test_reader_models_are_distinct_vision_models(self):
        assert len(CAPTCHA_READER_MODELS) == 3
        assert len(set(CAPTCHA_READER_MODELS)) == 3
        for model_id in CAPTCHA_READER_MODELS:
            spec = CanonicalModelRegistry.resolve_spec(model_id)
            assert spec.canonical_id == model_id, f"{model_id} chưa được đăng ký trong registry"
            assert AiCapability.MULTIMODAL_VISION in spec.capabilities

    def test_secondary_reader_has_no_silent_fallback(self):
        """Model đọc thứ 2 không được tự fallback sang model khác (mất tính độc lập)."""
        spec = CanonicalModelRegistry.resolve_spec(SECONDARY)
        assert spec.fallback_canonical_id is None

    def test_two_models_agree_returns_value_without_arbiter(self, monkeypatch):
        gw = _FakeGateway({PRIMARY: "WBC4EJ", SECONDARY: "wbc4ej", ARBITER: "XXXXXX"})
        _install_gateway(monkeypatch, gw)

        value, provider = InvoiceGdtCaptchaMixin()._solve_with_ai_gateway("data:image/png;base64,AAAA")

        assert value == "WBC4EJ"
        assert PRIMARY in provider and SECONDARY in provider
        assert gw.calls == [PRIMARY, SECONDARY], "Không được gọi trọng tài khi 2 model đầu đã khớp"

    def test_disagreement_resolved_by_arbiter(self, monkeypatch):
        gw = _FakeGateway({PRIMARY: "MD3F8C", SECONDARY: "MD3FBC", ARBITER: "MD3FBC"})
        _install_gateway(monkeypatch, gw)

        value, provider = InvoiceGdtCaptchaMixin()._solve_with_ai_gateway("data:image/png;base64,AAAA")

        assert value == "MD3FBC"
        assert SECONDARY in provider and ARBITER in provider
        assert gw.calls == [PRIMARY, SECONDARY, ARBITER]

    def test_total_disagreement_returns_none_and_explains(self, monkeypatch):
        gw = _FakeGateway({PRIMARY: "MD3F8C", SECONDARY: "MD3FBC", ARBITER: "HD3FBC"})
        _install_gateway(monkeypatch, gw)

        solver = InvoiceGdtCaptchaMixin()
        value, provider = solver._solve_with_ai_gateway("data:image/png;base64,AAAA")

        assert value is None and provider is None
        assert "không thống nhất" in solver._last_captcha_error
        assert "MD3F8C" in solver._last_captcha_error

    def test_same_underlying_model_does_not_count_as_consensus(self, monkeypatch):
        """Gateway fallback âm thầm về cùng 1 model -> các phiếu trùng model chỉ tính 1."""
        gw = _FakeGateway(
            {PRIMARY: "ABC123", SECONDARY: "ABC123", ARBITER: "ABC123"},
            model_used_override="openrouter/gemini-3.8-flash",
        )
        _install_gateway(monkeypatch, gw)

        value, _ = InvoiceGdtCaptchaMixin()._solve_with_ai_gateway("data:image/png;base64,AAAA")
        assert value is None

    def test_failed_reader_is_skipped(self, monkeypatch):
        gw = _FakeGateway({PRIMARY: "Q7KX2P", SECONDARY: RuntimeError("503"), ARBITER: "Q7KX2P"})
        _install_gateway(monkeypatch, gw)

        value, _ = InvoiceGdtCaptchaMixin()._solve_with_ai_gateway("data:image/png;base64,AAAA")
        assert value == "Q7KX2P"

    def test_invalid_length_readings_do_not_vote(self, monkeypatch):
        gw = _FakeGateway({PRIMARY: "AB", SECONDARY: "AB", ARBITER: "AB"})
        _install_gateway(monkeypatch, gw)

        value, _ = InvoiceGdtCaptchaMixin()._solve_with_ai_gateway("data:image/png;base64,AAAA")
        assert value is None


# --------------------------------------------------------------------------
# 2. VIETNAM TIME DISPLAY (GMT+7)
# --------------------------------------------------------------------------
class TestVietnamTimeDisplay:
    def test_format_vn_time_from_aware_utc(self):
        from app.core.vn_time import format_vn_time

        dt = datetime(2026, 10, 3, 1, 18, 10, tzinfo=timezone.utc)
        assert format_vn_time(dt) == "03/10/2026 08:18:10 (GMT+7)"

    def test_format_vn_time_from_iso_string(self):
        from app.core.vn_time import format_vn_time

        assert format_vn_time("2026-10-03T04:18:10.018277+00:00") == "03/10/2026 11:18:10 (GMT+7)"
        assert format_vn_time("2026-09-22T10:00:00Z") == "22/09/2026 17:00:00 (GMT+7)"

    def test_format_vn_time_passthrough_for_garbage(self):
        from app.core.vn_time import format_vn_time

        assert format_vn_time("không phải ngày giờ") == "không phải ngày giờ"

    def test_format_vn_clock_from_naive_local(self):
        """Datetime naive = giờ hệ thống (UTC trên Cloud) -> quy đổi đúng sang giờ VN."""
        from app.core.vn_time import VN_TZ, format_vn_clock

        naive_local = datetime(2026, 10, 3, 1, 17, 0)
        expected = naive_local.astimezone(VN_TZ).strftime("%H:%M")
        assert format_vn_clock(naive_local) == expected

    @pytest.mark.anyio
    async def test_failure_alert_email_shows_vietnam_time(self):
        from app.modules.invoices.application.invoice_processing.auto_sync_scheduler import (
            InvoiceAutoSyncSchedulerService,
        )

        service = InvoiceAutoSyncSchedulerService(invoice_service=object())
        with patch(
            "app.modules.core.application.alert_service.BrevoEmailService.send_email",
            new_callable=AsyncMock,
            return_value={"success": True},
        ) as mock_send:
            await service.send_sync_failure_alert(slot_label="08:17", error_message="Mã captcha không đúng.")

        kwargs = mock_send.call_args.kwargs
        assert "(GMT+7)" in kwargs["subject"]
        assert "(GMT+7)" in kwargs["html_content"]

    @pytest.mark.anyio
    async def test_circuit_breaker_alert_email_shows_vietnam_time(self):
        from app.modules.invoices.application.invoice_processing.auto_sync_scheduler import (
            InvoiceAutoSyncSchedulerService,
        )

        service = InvoiceAutoSyncSchedulerService(invoice_service=object())
        with patch(
            "app.modules.core.application.alert_service.BrevoEmailService.send_email",
            new_callable=AsyncMock,
            return_value={"success": True},
        ) as mock_send:
            await service.send_circuit_breaker_alert(slot_label="20:05", error_message="2 lần thất bại")

        kwargs = mock_send.call_args.kwargs
        assert "(GMT+7)" in kwargs["subject"]
        assert "(GMT+7)" in kwargs["html_content"]

    def test_cooldown_messages_show_vietnam_time(self):
        from app.modules.invoices.application.invoice_processing import InvoiceProcessingService

        svc = InvoiceProcessingService()
        cd_iso = "2026-10-03T04:18:10.018277+00:00"

        # (a) Đang trong cooldown -> bỏ qua phiên
        with patch.object(svc, "check_auth_cooldown", return_value=(True, cd_iso)):
            res = svc._execute_real_gdt_connection(
                config_id=uuid.uuid4(), tax_code="0202111150", username="0202111150",
                password="x", extra={}, from_date="2026-09-01", to_date="2026-10-03",
            )
        assert "11:18:10 (GMT+7)" in res["message"]
        assert "+00:00" not in res["message"]
        assert res["cooldown_until"] == cd_iso  # giá trị máy đọc giữ nguyên ISO UTC

        # (b) Đăng nhập thất bại -> kích hoạt cooldown
        with patch.object(svc, "check_auth_cooldown", return_value=(False, None)), patch.object(
            svc, "_authenticate_gdt_portal",
            return_value={"status": "auth_error", "message": "Mã captcha không đúng."},
        ), patch.object(svc, "set_auth_cooldown", return_value=cd_iso), patch.object(
            svc, "_update_sync_status"
        ):
            res = svc._execute_real_gdt_connection(
                config_id=uuid.uuid4(), tax_code="0202111150", username="0202111150",
                password="x", extra={}, from_date="2026-09-01", to_date="2026-10-03",
            )
        assert "11:18:10 (GMT+7)" in res["message"]
        assert "+00:00" not in res["message"]
