import asyncio
import base64
import concurrent.futures
import json
import logging
import re
import ssl
import urllib.error
import urllib.request
from typing import Any

from playwright.sync_api import sync_playwright

from app.core.settings import get_settings

logger = logging.getLogger(__name__)

# --- Cấu hình đồng thuận đa model khi giải captcha Cổng Thuế ---------------------------------
# Thứ tự = thứ tự hỏi. 2 model đầu rẻ & nhanh; model thứ 3 mạnh hơn, chỉ được gọi làm "trọng tài"
# khi 2 model đầu đọc lệch nhau. Chi phí điển hình: 2 lượt gọi (< 0.001 USD / captcha).
CAPTCHA_READER_MODELS: tuple[str, str, str] = (
    "openrouter/gemini-2.5-flash",       # người đọc chính
    "openrouter/gemini-2.5-flash-lite",  # người đọc độc lập (không fallback - xem registry)
    "openrouter/gemini-3.8-flash",       # trọng tài khi 2 model đầu lệch nhau
)
CAPTCHA_MIN_AGREEING_MODELS = 2
CAPTCHA_MIN_LEN = 4
CAPTCHA_MAX_LEN = 6
CAPTCHA_READ_TIMEOUT_SECONDS = 20.0
CAPTCHA_PROMPT = (
    "Transcribe ONLY the 4 to 6 uppercase alphanumeric characters seen in this captcha image. "
    "Output ONLY the raw characters (no spaces, no punctuation, no markdown)."
)
# Phóng to ảnh trước khi OCR: SVG gốc chỉ 200x40px, nét chữ mảnh -> model dễ nhầm O/Q, X/K, 1/I.
CAPTCHA_RENDER_SCALE = 3

# Captcha Cổng Thuế sinh bởi thư viện svg-captcha:
#   - mỗi KÝ TỰ là đúng 1 <path fill="#xxx" d="..."/> (tô đặc),
#   - mỗi ĐƯỜNG NHIỄU là 1 <path d="..." stroke="#xxx" fill="none"/> (chỉ có nét).
# => Loại bỏ path fill="none" là xoá sạch nhiễu mà không đụng tới nét chữ (khảo sát 04/10/2026).
_NOISE_PATH_RE = re.compile(
    r"""<path\b[^>]*\bfill\s*=\s*["']none["'][^>]*?(?:/>|>\s*</path>)""",
    re.IGNORECASE,
)
_PATH_TAG_RE = re.compile(r"<path\b[^>]*>", re.IGNORECASE)


def clean_captcha_svg(svg: str | None) -> str:
    """Xoá các đường nhiễu (path fill="none") khỏi SVG captcha, giữ nguyên các path ký tự."""
    if not svg:
        return ""
    return _NOISE_PATH_RE.sub("", svg)


def count_captcha_glyphs(svg: str | None) -> int | None:
    """Suy ra số ký tự captcha = số path tô đặc sau khi loại nhiễu.

    Trả None nếu kết quả nằm ngoài khoảng hợp lệ [CAPTCHA_MIN_LEN, CAPTCHA_MAX_LEN]
    (SVG lạ / đổi định dạng) để luồng gọi quay về kiểm tra độ dài mềm 4-6 như cũ.
    """
    cleaned = clean_captcha_svg(svg)
    if not cleaned:
        return None
    n = len(_PATH_TAG_RE.findall(cleaned))
    return n if CAPTCHA_MIN_LEN <= n <= CAPTCHA_MAX_LEN else None


def build_captcha_prompt(expected_len: int | None = None) -> str:
    """Prompt OCR; khi biết trước độ dài thì nói rõ để model không đọc thiếu/thừa ký tự."""
    if not expected_len:
        return CAPTCHA_PROMPT
    return (
        f"This captcha image contains exactly {expected_len} uppercase alphanumeric characters. "
        f"Transcribe all {expected_len} characters from left to right. "
        "Output ONLY the raw characters (no spaces, no punctuation, no markdown)."
    )


def clean_captcha_text(raw_text: str | None) -> str:
    """Làm sạch chuỗi ký tự nhận diện từ Vision AI về đúng chuẩn mã Captcha Cổng Thuế (4-8 ký tự in hoa).
    Hỗ trợ xử lý mọi dạng phản hồi từ LLM: chuỗi trần, khối code markdown, câu giải thích hội thoại.
    """
    if not raw_text:
        return ""
    # Loại bỏ code blocks, json formatting hoặc text rác
    cleaned = raw_text.strip()
    if "```" in cleaned:
        cleaned = (
            cleaned.split("```")[-2]
            if len(cleaned.split("```")) > 2
            else cleaned.replace("```", "")
        )
        cleaned = cleaned.strip()

    if ":" in cleaned and not cleaned.startswith("http"):
        # Lấy phần sau dấu hai chấm nếu AI trả về "Captcha: ABCDE"
        parts = cleaned.split(":")
        cleaned = parts[-1].strip()

    # Nếu chuỗi có nhiều dòng, ưu tiên dòng cuối cùng
    lines = [ln.strip() for ln in cleaned.splitlines() if ln.strip()]
    if lines:
        cleaned = lines[-1]

    # Kiểm tra trực tiếp alphanumeric
    alphanumeric = re.sub(r"[^A-Za-z0-9]", "", cleaned).upper()
    if 3 <= len(alphanumeric) <= 8:
        return alphanumeric

    # Nếu vẫn chưa đạt độ dài chuẩn do LLM thêm từ nối (vd: "The characters are B5QHX8."),
    # trích xuất các token alphanumeric có độ dài 3-8 ký tự từ văn bản gốc
    noise_words = {
        "CAPTCHA", "IMAGE", "CHARACTERS", "CHARACTER", "LETTERS", "LETTER",
        "NUMBER", "NUMBERS", "SHOWN", "OUTPUT", "RESULT", "CODE", "TEXT",
        "FOUND", "VALUE", "SEEN", "READ", "TRANSCRIBE", "TRANSCRIBED", "HERE",
        "THIS", "THAT", "PLEASE", "ONLY", "VERIFICATION", "ENTER", "SOLVE",
        "SOLVED", "FOLLOWING", "ABOVE", "BELOW", "VALID", "INVALID", "INPUT"
    }
    tokens = re.findall(r"\b[A-Za-z0-9]{3,8}\b", raw_text)
    valid_candidates = [
        t.upper() for t in tokens if t.upper() not in noise_words
    ]
    if valid_candidates:
        mixed_candidates = [c for c in valid_candidates if any(ch.isdigit() for ch in c) and any(ch.isalpha() for ch in c)]
        if mixed_candidates:
            return mixed_candidates[-1]
        return valid_candidates[-1]

    return alphanumeric


class InvoiceGdtCaptchaMixin:
    """Mixin for multi-tier GDT captcha resolution via AI Vision OCR."""

    def _render_svg_to_png(self, svg_content: str) -> bytes:
        """Render mã Captcha SVG thành ảnh PNG cho Vision AI bằng PyMuPDF C++ (fallback Playwright).

        Tiền xử lý (sự cố 04/10/2026): loại các đường nhiễu fill="none" và phóng to
        CAPTCHA_RENDER_SCALE lần trên nền trắng đặc -> các model đọc thống nhất hơn hẳn
        (A/B 6 captcha thật: đồng thuận tuyệt đối 3/3 model tăng từ 3/6 lên 5/6 ảnh).
        """
        svg_content = clean_captcha_svg(svg_content) or svg_content

        # 1. Primary ultra-fast C++ rendering via PyMuPDF (0 external browser dependencies, ~1ms)
        try:
            import pymupdf
            doc = pymupdf.open("svg", svg_content.encode("utf-8"))
            pix = doc[0].get_pixmap(
                matrix=pymupdf.Matrix(CAPTCHA_RENDER_SCALE, CAPTCHA_RENDER_SCALE),
                alpha=False,  # nền trắng đặc thay vì trong suốt
            )
            png_bytes = pix.tobytes("png")
            if png_bytes and len(png_bytes) > 50:
                return png_bytes
        except Exception as py_err:
            logger.warning(f"PyMuPDF SVG render fallback to Playwright: {py_err}")

        # 2. Secondary fallback via Playwright headless Chromium
        def _do_render():
            p = sync_playwright().start()
            try:
                browser = p.chromium.launch(headless=True)
                try:
                    page = browser.new_page(
                        viewport={"width": 200, "height": 40},
                        device_scale_factor=CAPTCHA_RENDER_SCALE,
                    )
                    page.set_content(
                        f"<html><body style='margin:0;padding:0;background:white;'>{svg_content}</body></html>"
                    )
                    return page.screenshot(type="png")
                finally:
                    browser.close()
            finally:
                p.stop()

        try:
            loop = None
            try:
                loop = asyncio.get_running_loop()
            except RuntimeError:
                pass

            if loop and loop.is_running():
                with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
                    return pool.submit(_do_render).result(timeout=15)
            else:
                return _do_render()
        except Exception as e:
            logger.error(f"Playwright render error: {e}")
            raise

    def _read_captcha_once(
        self, data_uri: str, canonical_model_id: str, expected_len: int | None = None
    ) -> tuple[str | None, str | None]:
        """Một lượt đọc captcha bằng 1 model qua Enterprise AI Gateway.

        Trả về (chuỗi đã làm sạch, model_used thực tế) hoặc (None, None) nếu lỗi / sai định dạng.
        `model_used` là model THỰC SỰ trả lời (có thể khác model yêu cầu nếu gateway fallback),
        dùng để đảm bảo các phiếu đồng thuận đến từ các model khác nhau.
        `expected_len` (số ký tự suy ra từ SVG): nếu có, loại mọi bản đọc khác độ dài này
        (vd. flash-lite đọc thiếu 'KXM51' cho ảnh 6 ký tự trong sự cố 04/10/2026).
        """
        from app.core.ai.domain.models import AiVisionRequest
        from app.core.ai.infrastructure.gateway import get_ai_gateway

        req = AiVisionRequest(
            prompt=build_captcha_prompt(expected_len),
            data_uri=data_uri,
            canonical_model_id=canonical_model_id,
            temperature=0.0,
            max_tokens=50,
        )
        try:
            resp = get_ai_gateway().analyze_vision_sync(req, timeout=CAPTCHA_READ_TIMEOUT_SECONDS)
        except Exception as e:
            logger.warning("[GDT_CAPTCHA] Model %s lỗi khi đọc captcha: %s", canonical_model_id, e)
            return None, None

        cleaned = clean_captcha_text(resp.content)
        length_ok = (
            len(cleaned) == expected_len
            if expected_len
            else CAPTCHA_MIN_LEN <= len(cleaned) <= CAPTCHA_MAX_LEN
        )
        if not length_ok:
            logger.warning(
                "[GDT_CAPTCHA] Model %s trả về chuỗi sai định dạng: '%s' (cần %s ký tự, raw: '%s')",
                resp.model_used,
                cleaned,
                expected_len or f"{CAPTCHA_MIN_LEN}-{CAPTCHA_MAX_LEN}",
                (resp.content or "")[:60],
            )
            return None, None
        return cleaned, resp.model_used

    def _solve_with_ai_gateway(
        self, data_uri: str, expected_len: int | None = None
    ) -> tuple[str | None, str | None]:
        """Giải mã captcha bằng cơ chế ĐỒNG THUẬN ĐA MODEL (Multi-model Consensus).

        Bối cảnh (sự cố 03/10/2026): 1 model đọc nhầm 1 ký tự ('MD3F8C') -> Cổng Thuế từ chối,
        tốn 1 lượt đăng nhập và kích hoạt cooldown 3 giờ. Hai lần như vậy liên tiếp sẽ ngắt mạch
        toàn bộ lịch đồng bộ.

        Quy tắc:
          1. Lần lượt hỏi các model trong CAPTCHA_READER_MODELS (khác kiến trúc -> lỗi độc lập).
             Không gọi lặp cùng 1 model vì temperature=0 sẽ lặp lại đúng kết quả sai.
          2. Chấp nhận ngay khi có >= CAPTCHA_MIN_AGREEING_MODELS model KHÁC NHAU (theo
             model_used thực tế) trả về cùng 1 chuỗi. Thường chỉ tốn 2 lượt gọi.
          3. Không đồng thuận -> trả (None, None): luồng gọi sẽ đổi ảnh captcha mới, KHÔNG gửi
             mã chưa chắc chắn lên Cổng Thuế.
          4. `expected_len` (từ count_captcha_glyphs) được truyền cho từng model: bản đọc sai độ
             dài bị loại, không được tính phiếu.
        """
        votes: dict[str, set[str]] = {}
        readings: list[str] = []

        for model_id in CAPTCHA_READER_MODELS:
            value, model_used = self._read_captcha_once(data_uri, model_id, expected_len)
            if not value or not model_used:
                readings.append(f"{model_id}=<lỗi>")
                continue

            readings.append(f"{model_used}={value}")
            voters = votes.setdefault(value, set())
            voters.add(model_used)

            if len(voters) >= CAPTCHA_MIN_AGREEING_MODELS:
                logger.info(
                    "[GDT_CAPTCHA] Đồng thuận captcha [%s] từ %s (các lượt đọc: %s)",
                    value,
                    sorted(voters),
                    ", ".join(readings),
                )
                return value, f"AI Gateway consensus [{' + '.join(sorted(voters))}]"

        self._last_captcha_error = (
            f"Các model AI không thống nhất kết quả đọc captcha ({', '.join(readings)}). "
            f"Đổi ảnh mới thay vì gửi mã chưa chắc chắn lên Cổng Thuế."
        )
        logger.warning("[GDT_CAPTCHA] %s", self._last_captcha_error)
        return None, None

    def _solve_with_openrouter(
        self, data_uri: str, api_key: str = ""
    ) -> tuple[str | None, str | None]:
        """Backward compatibility hook for test mocks. In production, delegates to AI Gateway."""
        return None, None

    def _solve_with_gemini_direct(
        self, data_uri: str, api_key: str = "", base_url: str = ""
    ) -> tuple[str | None, str | None]:
        """Backward compatibility hook for test mocks. In production, delegates to AI Gateway."""
        return None, None

    def _solve_gdt_captcha(
        self, extra_config: dict[str, Any] | None = None
    ) -> tuple[str, str]:
        """Tải mã Captcha SVG từ Cổng Thuế và giải mã tự động bằng Enterprise AI Gateway với cơ chế thử lại tối đa 3 lần."""
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        max_attempts = 3
        last_failure_reason = "Không thể giải mã Captcha"

        for attempt in range(1, max_attempts + 1):
            # 1. Fetch live captcha SVG from GDT Portal
            req = urllib.request.Request(
                "https://hoadondientu.gdt.gov.vn/api/captcha",
                headers={
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 DSConsERP/2.4.0"
                },
            )
            try:
                with urllib.request.urlopen(req, timeout=10, context=ctx) as r:
                    data = json.loads(r.read().decode("utf-8"))
                    ckey = data.get("key", "")
                    svg = data.get("content", "")
            except Exception as exc:
                last_failure_reason = f"Không thể lấy mã Captcha từ Cổng Tổng Cục Thuế: {exc}"
                logger.warning(f"[GDT_CAPTCHA] Lần {attempt}/{max_attempts}: {last_failure_reason}")
                if attempt < max_attempts:
                    continue
                raise ValueError(last_failure_reason)

            if not ckey or not svg:
                last_failure_reason = "Không thể lấy mã Captcha từ Cổng Tổng Cục Thuế (phản hồi rỗng)."
                logger.warning(f"[GDT_CAPTCHA] Lần {attempt}/{max_attempts}: {last_failure_reason}")
                if attempt < max_attempts:
                    continue
                raise ValueError(last_failure_reason)

            # 2. Render SVG to PNG
            try:
                png_bytes = self._render_svg_to_png(svg)
            except Exception as e:
                logger.error(f"Lỗi render SVG Captcha sang PNG: {e}")
                if attempt < max_attempts:
                    continue
                raise ValueError(f"Lỗi render SVG Captcha sang ảnh PNG: {e}")

            png_b64 = base64.b64encode(png_bytes).decode("utf-8")
            data_uri = f"data:image/png;base64,{png_b64}"

            # 3. Enterprise AI Vision Gateway Giải Mã (Antigravity SDK -> OpenRouter Fallback)
            cvalue, provider_used = self._solve_with_openrouter(data_uri, "")
            if not cvalue:
                cvalue, provider_used = self._solve_with_gemini_direct(data_uri, "", "")
            if not cvalue:
                cvalue, provider_used = self._solve_with_ai_gateway(
                    data_uri, expected_len=count_captcha_glyphs(svg)
                )

            if cvalue:
                logger.info(
                    f"Solved GDT Captcha (lần {attempt}/{max_attempts}): [{cvalue}] for key [{ckey}] via {provider_used}"
                )
                return ckey, cvalue

            err_detail = getattr(self, "_last_captcha_error", None) or "Lỗi kết nối AI Gateway"
            last_failure_reason = err_detail
            logger.warning(
                f"[GDT_CAPTCHA] Lần {attempt}/{max_attempts} giải mã chưa thành công ({err_detail}). Đang thử lại với Captcha mới..."
            )

        raise ValueError(
            f"Không thể giải mã Captcha Cổng Thuế qua Enterprise AI Gateway sau {max_attempts} lần thử ({last_failure_reason}). Vui lòng kiểm tra lại cấu hình Gemini/OpenRouter API Key."
        )

    def _solve_gdt_captcha_with_openrouter(self) -> tuple[str, str]:
        """Wrapper giữ tương thích ngược."""
        return self._solve_gdt_captcha()
