from __future__ import annotations

"""GDT Portal authentication and connection testing."""


import imaplib
import json
import logging
import ssl
import urllib.error
import urllib.request
from typing import Any

from .gdt_captcha import InvoiceGdtCaptchaMixin

logger = logging.getLogger(__name__)


AIHAWK_STEALTH_JS = """
// 1. Mask navigator.webdriver
Object.defineProperty(navigator, 'webdriver', {
    get: () => undefined,
});

// 2. Inject realistic window.chrome runtime
window.chrome = {
    runtime: {},
    app: {},
    loadTimes: function() {},
    csi: function() {}
};

// 3. Emulate platform, languages and realistic plugins
Object.defineProperty(navigator, 'platform', {
    get: () => 'Win32',
});

Object.defineProperty(navigator, 'languages', {
    get: () => ['vi-VN', 'vi', 'en-US', 'en'],
});

Object.defineProperty(navigator, 'plugins', {
    get: () => [1, 2, 3, 4, 5],
});

// 4. Overwrite notification permissions query
if (window.navigator && window.navigator.permissions) {
    const origQuery = window.navigator.permissions.query;
    window.navigator.permissions.query = (params) => (
        params && params.name === 'notifications' ?
            Promise.resolve({ state: Notification.permission }) :
            origQuery(params)
    );
}
"""

GDT_MAX_AUTH_ATTEMPTS = 1

# --- Đổi ảnh captcha trên modal Đăng nhập Cổng Thuế ------------------------------------------
# DOM thực tế (khảo sát bằng Playwright ngày 04/10/2026, không đăng nhập):
#   <div class="Captcha__ImageWrapper-..."><img alt="captcha" src="data:image/svg+xml;base64,...">
#     <button type="button" class="ant-btn ... ant-btn-icon-only"><i class="anticon"><svg .../>
# Selector cũ `.anticon-reload, span:has(svg), ...` khớp NHẦM nút đóng modal
# (`span.ant-modal-close-x` chứa <svg>) -> ảnh không bao giờ đổi, AI đọc lại cùng 1 ảnh 3 lần
# rồi báo AI_ERROR (sự cố 08:15 ngày 04/10/2026). Bấm vào chính <img> cũng KHÔNG đổi ảnh.
# Tuyệt đối không dùng selector chung chung kiểu `span:has(svg)` vì dễ trúng nút đóng modal.
CAPTCHA_IMG_SELECTOR = "img[alt='captcha']"
CAPTCHA_REFRESH_SELECTORS: tuple[str, ...] = (
    "img[alt='captcha'] + button",
    "[class*='Captcha__ImageWrapper'] button",
    "img + button",
)
CAPTCHA_MAX_IMAGES = 3
CAPTCHA_REFRESH_POLL_MS = 300
CAPTCHA_REFRESH_POLL_TIMES = 12


def extract_svg_from_img_src(src: str | None) -> str | None:
    """Giải mã SVG từ thuộc tính src dạng data URI base64 của thẻ <img> captcha."""
    import base64

    if not src or "base64," not in src:
        return None
    try:
        return base64.b64decode(src.split("base64,", 1)[1]).decode("utf-8")
    except Exception as b64_err:
        logger.warning("[GDT_AUTH] Lỗi decode SVG từ img.src: %s", b64_err)
        return None


class InvoiceGdtAuthMixin(InvoiceGdtCaptchaMixin):
    def _refresh_captcha_image(self, page: Any, modal: Any, cap_img: Any, old_src: str) -> bool:
        """Bấm nút đổi ảnh captcha và XÁC NHẬN src thực sự thay đổi.

        Thử lần lượt CAPTCHA_REFRESH_SELECTORS; trả True ngay khi ảnh đổi, False nếu không
        selector nào làm đổi ảnh (luồng gọi phải dừng, không đọc lại ảnh cũ).
        """
        for selector in CAPTCHA_REFRESH_SELECTORS:
            try:
                btn = modal.locator(selector).first
                if not btn.count() or not btn.is_visible():
                    continue
                btn.click()
            except Exception as click_err:
                logger.debug("[GDT_AUTH] Không bấm được '%s': %s", selector, click_err)
                continue
            for _ in range(CAPTCHA_REFRESH_POLL_TIMES):
                page.wait_for_timeout(CAPTCHA_REFRESH_POLL_MS)
                if (cap_img.get_attribute("src") or "") != old_src:
                    return True
            logger.warning("[GDT_AUTH] Bấm '%s' nhưng ảnh captcha không đổi.", selector)
        return False

    def _acquire_captcha_value(
        self, page: Any, modal: Any, cap_img: Any, latest_captcha: dict[str, Any]
    ) -> tuple[str | None, str | None]:
        """Đọc captcha trên modal; tối đa CAPTCHA_MAX_IMAGES ảnh KHÁC NHAU.

        - Mỗi ảnh chỉ được gửi cho AI đúng 1 lần (so dấu vân tay SVG) -> không tốn tiền đọc lại
          ảnh cũ và không tự lừa mình rằng đã "đổi ảnh".
        - Số ký tự suy ra từ SVG (count_captcha_glyphs) được dùng để loại bản đọc thiếu/thừa.
        - Nếu không đổi được ảnh -> dừng sớm, trả (None, None) để hủy phiên (KHÔNG submit).
        """
        import base64
        import hashlib

        from .gdt_captcha import count_captcha_glyphs

        seen: set[str] = set()
        self._last_captcha_error = None  # tránh mang lỗi cũ của phiên trước vào email cảnh báo
        for cap_try in range(1, CAPTCHA_MAX_IMAGES + 1):
            src = cap_img.get_attribute("src") or ""
            svg = extract_svg_from_img_src(src) or latest_captcha.get("svg")
            fingerprint = hashlib.sha1(svg.encode("utf-8")).hexdigest() if svg else None

            if svg and fingerprint not in seen:
                seen.add(fingerprint)
                expected_len = count_captcha_glyphs(svg)
                png_bytes = self._render_svg_to_png(svg)
                data_uri = f"data:image/png;base64,{base64.b64encode(png_bytes).decode('utf-8')}"
                solved, prov_name = self._solve_with_ai_gateway(data_uri, expected_len=expected_len)
                if solved and 4 <= len(solved) <= 6:
                    logger.info(
                        "✅ [GDT_AUTH] Giải mã Captcha thành công ở ảnh %d/%d: [%s] (%s)",
                        cap_try,
                        CAPTCHA_MAX_IMAGES,
                        solved,
                        prov_name,
                    )
                    return solved, prov_name
                logger.warning(
                    "⚠️ [GDT_AUTH] Ảnh %d/%d: AI chưa đồng thuận ('%s'). Đổi ảnh mới...",
                    cap_try,
                    CAPTCHA_MAX_IMAGES,
                    solved,
                )

            if cap_try >= CAPTCHA_MAX_IMAGES:
                break
            if not self._refresh_captcha_image(page, modal, cap_img, src):
                logger.warning(
                    "🛡️ [GDT_AUTH] Không đổi được ảnh captcha (nút đổi ảnh không phản hồi). Dừng để không đọc lại ảnh cũ."
                )
                break
        return None, None

    def _authenticate_via_playwright(
        self,
        tax_code: str,
        username: str,
        password: str,
        extra_config: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Xác thực với Cổng Tổng Cục Thuế thông qua Playwright headless Chromium với AIHawk Stealth để vượt WAF F5 BIG-IP ASM."""
        import base64
        import concurrent.futures
        from playwright.sync_api import sync_playwright

        def _do_auth():
            with sync_playwright() as p:
                browser = p.chromium.launch(
                    headless=True,
                    args=[
                        "--disable-blink-features=AutomationControlled",
                        "--disable-infobars",
                        "--no-sandbox",
                        "--disable-setuid-sandbox",
                        "--disable-dev-shm-usage",
                        "--disable-web-security",
                        "--ignore-certificate-errors",
                    ],
                )
                try:
                    context = browser.new_context(
                        user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
                        viewport={"width": 1366, "height": 768},
                        locale="vi-VN",
                        timezone_id="Asia/Ho_Chi_Minh",
                        ignore_https_errors=True,
                        extra_http_headers={
                            "Accept-Language": "vi-VN,vi;q=0.9,en-US;q=0.8,en;q=0.7",
                            "Sec-Ch-Ua": '"Chromium";v="133", "Google Chrome";v="133", "Not-A.Brand";v="99"',
                            "Sec-Ch-Ua-Mobile": "?0",
                            "Sec-Ch-Ua-Platform": '"Windows"',
                        },
                    )
                    context.add_init_script(AIHAWK_STEALTH_JS)
                    page = context.new_page()

                    latest_captcha = {"key": None, "svg": None}
                    captured_jwt = None
                    last_err_msg = "Không xác thực được với Cổng Thuế."
                    last_solved_cvalue = None

                    def handle_response(resp):
                        nonlocal captured_jwt, last_err_msg
                        url = resp.url
                        if "/api/captcha" in url and resp.status == 200:
                            try:
                                data = resp.json()
                                latest_captcha["key"] = data.get("key")
                                latest_captcha["svg"] = data.get("content")
                            except Exception:
                                pass

                        if "/api/security-taxpayer/authenticate" in url:
                            try:
                                data = resp.json()
                                if resp.status == 200:
                                    captured_jwt = data.get("token") or data.get("id_token")
                                else:
                                    last_err_msg = data.get("message") or data.get("error") or "Lỗi xác thực Cổng Thuế"
                            except Exception:
                                pass

                    page.on("response", handle_response)
                    page.goto("https://hoadondientu.gdt.gov.vn/", timeout=35000, wait_until="networkidle")

                    # Đóng modal thông báo giả mạo nếu có
                    close_btn = page.locator(".ant-modal-close, button.close, [aria-label='Close'], .ant-modal-close-x").first
                    if close_btn.is_visible():
                        close_btn.click()
                        page.wait_for_timeout(600)

                    # Bấm nút "Đăng nhập" ở navbar
                    login_nav = page.locator("header, nav, .ant-layout-header, .header").locator("text=Đăng nhập").first
                    if not login_nav.is_visible():
                        login_nav = page.locator("button:has-text('Đăng nhập'), a:has-text('Đăng nhập')").first
                    if login_nav.is_visible():
                        login_nav.click()
                        page.wait_for_timeout(1000)

                    modal = page.locator(".ant-modal-content").last
                    user_input = modal.locator("#username, input[formcontrolname='username']").first
                    pass_input = modal.locator("#password, input[formcontrolname='password']").first
                    cap_input = modal.locator("#cvalue, input[formcontrolname='cvalue']").first
                    submit_btn = modal.locator("button:has-text('Đăng nhập'), button[type='submit']").last

                    # Quy tắc an toàn tuyệt đối:
                    # 1. Lấy mã Captcha trực tiếp từ thẻ <img> trên modal để đồng bộ 100% với form Angular.
                    # 2. Nếu AI không thể giải mã với độ tin cậy cao, DỪNG LẠI và KHÔNG bấm Đăng nhập.
                    # 3. Chỉ bấm Đăng nhập DUY NHẤT 1 LẦN khi mã Captcha đã sẵn sàng và hợp lệ.
                    # 4. Mỗi ảnh captcha chỉ đọc 1 lần; đổi ảnh bằng nút thật cạnh <img alt="captcha">
                    #    (xem CAPTCHA_REFRESH_SELECTORS), không bao giờ bấm trúng nút đóng modal.
                    cap_img = modal.locator(CAPTCHA_IMG_SELECTOR).first
                    if not cap_img.count():
                        cap_img = modal.locator("img").first

                    cvalue, prov = self._acquire_captcha_value(page, modal, cap_img, latest_captcha)

                    if not cvalue:
                        last_err_msg = (
                            getattr(self, "_last_captcha_error", None)
                            or "AI không thể giải mã Captcha với độ tin cậy tuyệt đối."
                        )
                        last_err_msg = (
                            f"AI không đồng thuận kết quả đọc Captcha sau tối đa {CAPTCHA_MAX_IMAGES} ảnh "
                            f"({last_err_msg}). Chủ động hủy phiên, KHÔNG gửi đăng nhập lên Cổng Thuế."
                        )
                        logger.warning("🛡️ [GDT_AUTH] %s", last_err_msg)
                        return {
                            "status": "ai_error",
                            "message": last_err_msg,
                            "is_auth_error": False,
                        }

                    last_solved_cvalue = cvalue

                    # Mô phỏng thao tác gõ phím tự nhiên (Human keystroke delay) để vượt qua phân tích hành vi của WAF
                    user_input.fill("")
                    try:
                        user_input.press_sequentially(username, delay=35)
                    except AttributeError:
                        user_input.type(username, delay=35)

                    pass_input.fill("")
                    try:
                        pass_input.press_sequentially(password, delay=35)
                    except AttributeError:
                        pass_input.type(password, delay=35)

                    cap_input.fill("")
                    try:
                        cap_input.press_sequentially(cvalue, delay=40)
                    except AttributeError:
                        cap_input.type(cvalue, delay=40)

                    page.wait_for_timeout(600)

                    # Bấm Đăng nhập DUY NHẤT 1 LẦN
                    submit_btn.click()
                    page.wait_for_timeout(4000)

                    # Kiểm tra thông báo lỗi trên UI nếu có
                    if not captured_jwt:
                        try:
                            modal_err = modal.locator(".ant-form-item-explain-error, .ant-alert-message, .ant-notification-notice-description, .ant-message-error").first
                            if modal_err.is_visible():
                                err_text = modal_err.inner_text().strip()
                                if err_text:
                                    last_err_msg = err_text
                        except Exception:
                            pass

                        logger.warning(
                            "[GDT_AUTH] Phiên thử xác thực Cổng Thuế duy nhất kết thúc. Phản hồi: '%s'. Không thử lại trong phiên này để chống khóa tài khoản.",
                            last_err_msg,
                        )

                    if captured_jwt:
                        cookies = context.cookies()
                        cookie_header = "; ".join([f"{c['name']}={c['value']}" for c in cookies])
                        return {
                            "status": "success",
                            "token": captured_jwt,
                            "cookie_header": cookie_header,
                            "cookies": cookies,
                            "solved_captcha": last_solved_cvalue,
                            "message": f"Xác thực và vượt Captcha Cổng Thuế thành công (MST: {tax_code}).",
                        }
                    else:
                        return {
                            "status": "auth_error",
                            "message": f"Cổng Tổng Cục Thuế phản hồi: {last_err_msg} (Đã thử qua AI Vision: [{last_solved_cvalue}]).",
                        }
                finally:
                    browser.close()

        # Thread safety check for running asyncio event loop in FastAPI
        try:
            loop = None
            try:
                import asyncio
                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_auth).result(timeout=60)
            else:
                return _do_auth()
        except Exception as exc:
            logger.error(f"[GDT_AUTH] Playwright browser auth failed: {exc}")
            return {
                "status": "connection_error",
                "message": f"Lỗi xác thực trình duyệt tự động tới GDT: {exc!s}",
            }

    def _authenticate_gdt_portal(
        self,
        tax_code: str,
        username: str,
        password: str,
        extra_config: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Tự động vượt Captcha bằng AI Vision OCR và xác thực với Cổng Tổng Cục Thuế (ưu tiên Playwright browser session để qua WAF)."""
        # 1. Primary path: Playwright browser session (bypass F5 BIG-IP WAF & ASM bot detection)
        try:
            res = self._authenticate_via_playwright(
                tax_code=tax_code,
                username=username,
                password=password,
                extra_config=extra_config,
            )
            if res.get("status") in ("success", "auth_error", "ai_error"):
                return res
            logger.warning("[GDT_AUTH] Playwright auth returned %s, falling back to HTTP...", res.get("status"))
        except Exception as pw_err:
            logger.warning("[GDT_AUTH] Playwright auth exception: %s. Falling back to HTTP...", pw_err)

        # 2. Secondary fallback path: Direct HTTP request
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        headers = {
            "Content-Type": "application/json",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
            "Accept": "application/json, text/plain, */*",
            "Referer": "https://hoadondientu.gdt.gov.vn/",
            "Origin": "https://hoadondientu.gdt.gov.vn",
        }

        last_error = "Không thể kết nối Cổng Tổng Cục Thuế."

        # Quy tắc an toàn: Chỉ thử đúng 1 lần (0 retry trong phiên)
        for attempt in range(1, 2):
            try:
                ckey, cvalue = self._solve_gdt_captcha(extra_config=extra_config)
            except Exception as e:
                logger.warning(f"[GDT_AUTH] Lỗi giải mã Captcha: {e}")
                return {
                    "status": "ai_error",
                    "message": f"Lỗi giải mã Captcha qua AI Vision: {e!s}",
                }

            auth_payload = json.dumps(
                {
                    "username": username,
                    "password": password,
                    "cvalue": cvalue,
                    "ckey": ckey,
                }
            ).encode("utf-8")

            try:
                gdt_req = urllib.request.Request(
                    "https://hoadondientu.gdt.gov.vn/api/security-taxpayer/authenticate",
                    data=auth_payload,
                    headers=headers,
                    method="POST",
                )
                with urllib.request.urlopen(
                    gdt_req, timeout=12, context=ctx
                ) as gdt_resp:
                    resp_data = json.loads(gdt_resp.read().decode("utf-8"))
                    token = resp_data.get("token") or resp_data.get("id_token")
                    return {
                        "status": "success",
                        "token": token,
                        "data": resp_data,
                        "solved_captcha": cvalue,
                        "message": f"Xác thực và vượt Captcha Cổng Thuế thành công (MST: {tax_code}).",
                    }
            except urllib.error.HTTPError as he:
                err_body = he.read().decode("utf-8", errors="ignore")
                try:
                    err_json = json.loads(err_body)
                    msg = err_json.get("message") or err_json.get("error") or err_body
                except Exception:
                    msg = err_body

                last_error = msg
                return {
                    "status": "auth_error",
                    "message": f"Cổng Tổng Cục Thuế phản hồi: {msg} (Đã vượt Captcha AI: [{cvalue}]).",
                }
            except Exception as exc:
                return {
                    "status": "connection_error",
                    "message": f"Lỗi kết nối mạng tới hoadondientu.gdt.gov.vn: {exc!s}",
                }

        return {
            "status": "auth_error",
            "message": f"Cổng Tổng Cục Thuế phản hồi: {last_error}",
        }

    def test_connection(
        self,
        service_type: str,
        tax_code: str,
        username: str,
        password: str,
        extra_config: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Kiểm tra thử nghiệm kết nối trực tiếp tới Cổng Tổng Cục Thuế (vượt Captcha tự động bằng AI) hoặc Hòm thư IMAP."""
        if not service_type or not username or not password:
            return {
                "status": "error",
                "message": "Vui lòng nhập đầy đủ Tên đăng nhập và Mật khẩu để kiểm tra kết nối.",
            }

        extra = extra_config or {}

        if service_type == "gdt_portal":
            auth_res = self._authenticate_gdt_portal(
                tax_code=tax_code,
                username=username,
                password=password,
                extra_config=extra,
            )
            if auth_res["status"] == "success":
                return {
                    "status": "success",
                    "message": f"Kết nối và xác thực thành công qua Cổng Tổng Cục Thuế cho MST {tax_code} (Đã vượt Captcha tự động bằng AI Vision OCR).",
                }
            else:
                return {
                    "status": auth_res.get("status", "auth_error"),
                    "message": auth_res.get("message", "Lỗi xác thực Cổng Thuế."),
                }

        elif service_type == "email_imap":
            server = extra.get("imap_server", "imap.gmail.com")
            port = int(extra.get("imap_port", 993))
            use_ssl = extra.get("use_ssl", True)
            try:
                if use_ssl:
                    mail = imaplib.IMAP4_SSL(server, port)
                else:
                    mail = imaplib.IMAP4(server, port)
                mail.login(username, password)
                mail.logout()
                return {
                    "status": "success",
                    "message": f"Kết nối và đăng nhập thành công hòm thư IMAP ({username}) tại máy chủ {server}:{port}.",
                }
            except Exception as exc:
                return {
                    "status": "auth_error",
                    "message": f"Lỗi đăng nhập hòm thư IMAP ({server}:{port}): {exc!s}. Vui lòng kiểm tra Địa chỉ Email và Mật khẩu ứng dụng (App Password).",
                }

        return {
            "status": "error",
            "message": f"Loại dịch vụ không được hỗ trợ: {service_type}",
        }
