from __future__ import annotations

"""Automatic Scheduled E-Invoice Synchronization Engine.

Executes automated synchronization daily at scheduled hours (when active):
01:00 and 13:00 (with random jitter ±30 minutes to evade anti-bot/spam rate limits).
If any failure, authentication error, or connection exception occurs,
an enterprise alert email is automatically dispatched to contact@dinhsonconstruction.com.
"""

import asyncio
import logging
import os
import random
import traceback
from datetime import date, datetime, timedelta
from typing import Any

from app.core.vn_time import format_vn_clock, format_vn_time
from app.modules.core.application.alert_service import BrevoEmailService
from app.modules.invoices.application.invoice_processing.service import (
    InvoiceProcessingService,
)

logger = logging.getLogger("dscons.invoice.auto_sync")

INVOICE_SYNC_ALERT_RECIPIENT = os.getenv(
    "INVOICE_SYNC_ALERT_EMAIL", "contact@dinhsonconstruction.com"
)
INVOICE_AUTO_SYNC_ENABLED = (
    os.getenv("INVOICE_AUTO_SYNC_ENABLED", "false").lower() in ("true", "1", "yes")
)
SCHEDULED_HOURS = [1, 13]
DEFAULT_JITTER_MINUTES = 30
DEFAULT_LOCKED_PAUSE_REASON = (
    "Tài khoản Cổng Tổng Cục Thuế (hoadondientu.gdt.gov.vn) bị tạm khóa do nhập sai nhiều lần. "
    "Cron job tự đồng bộ đang tạm ngưng chờ mở tài khoản."
)


class InvoiceAutoSyncSchedulerService:
    """Service điều phối tự động quét và đồng bộ hóa đơn theo khung giờ định kỳ (01:00 và 13:00)
    kèm biên độ ngẫu nhiên ±30 phút để chống spam và tránh WAF chặn IP."""

    def __init__(
        self,
        invoice_service: InvoiceProcessingService | None = None,
        alert_email: str = INVOICE_SYNC_ALERT_RECIPIENT,
        is_paused: bool = False,
        pause_reason: str | None = None,
        max_consecutive_auth_failures: int = 2,
        jitter_minutes: int = DEFAULT_JITTER_MINUTES,
        enable_jitter: bool = True,
    ) -> None:
        self._invoice_service = invoice_service or InvoiceProcessingService()
        self._alert_email = alert_email
        self._execution_history: list[dict[str, Any]] = []
        self._is_running: bool = False
        self._last_run_time: datetime | None = None
        self._last_run_status: str | None = None

        self._is_paused: bool = is_paused
        self._pause_reason: str = pause_reason or (
            DEFAULT_LOCKED_PAUSE_REASON if self._is_paused else ""
        )
        self._max_consecutive_auth_failures: int = max_consecutive_auth_failures
        self._jitter_minutes: int = max(0, jitter_minutes)
        self._enable_jitter: bool = enable_jitter
        self._last_scheduled_slot: tuple[date, int] | None = None

    @property
    def is_paused(self) -> bool:
        return self._is_paused

    @property
    def pause_reason(self) -> str:
        return self._pause_reason

    @property
    def max_consecutive_auth_failures(self) -> int:
        return self._max_consecutive_auth_failures

    @property
    def jitter_minutes(self) -> int:
        return self._jitter_minutes

    @property
    def enable_jitter(self) -> bool:
        return self._enable_jitter

    def pause(self, reason: str | None = None) -> None:
        """Tạm ngưng tiến trình chạy định kỳ (không kích hoạt quét Cổng Thuế)."""
        self._is_paused = True
        self._pause_reason = reason or DEFAULT_LOCKED_PAUSE_REASON
        logger.warning(
            "⏸️ [AUTO-SYNC] Đã tạm ngưng cron job tự đồng bộ hóa đơn. Lý do: %s",
            self._pause_reason,
        )

    def resume(self) -> None:
        """Khôi phục lại tiến trình tự động quét Cổng Thuế sau khi tài khoản được mở lại."""
        self._is_paused = False
        self._pause_reason = ""
        try:
            if hasattr(self._invoice_service, "clear_auth_cooldown"):
                self._invoice_service.clear_auth_cooldown("gdt_portal", "0202111150")
            if hasattr(self._invoice_service, "reset_auth_failures"):
                self._invoice_service.reset_auth_failures("gdt_portal", "0202111150")
            if hasattr(self._invoice_service, "activate_sync_config"):
                self._invoice_service.activate_sync_config("gdt_portal", "0202111150")
        except Exception:
            pass
        logger.info(
            "▶️ [AUTO-SYNC] Đã khôi phục hoạt động cho cron job tự đồng bộ hóa đơn định kỳ (Reset failure counter & clear cooldown)."
        )

    @property
    def scheduled_hours(self) -> list[int]:
        return list(SCHEDULED_HOURS)

    def scheduled_slots_vn_label(self) -> str:
        """Danh sách khung giờ chạy quy đổi sang giờ VN để hiển thị cho người dùng.

        SCHEDULED_HOURS được tính theo giờ hệ thống của máy chạy (container Cloud = UTC),
        nên [1, 13] trên Cloud tương ứng 08:00 và 20:00 giờ Việt Nam.
        """
        today = date.today()
        return ", ".join(
            format_vn_clock(datetime(today.year, today.month, today.day, h, 0, 0))
            for h in self.scheduled_hours
        )

    def calculate_next_run(
        self,
        current_time: datetime | None = None,
        with_jitter: bool | None = None,
        update_state: bool = False,
    ) -> tuple[datetime, float]:
        """Tính toán thời điểm và số giây chờ đến khung giờ chạy kế tiếp (01:00 hoặc 13:00).

        Hỗ trợ biên độ thời gian ngẫu nhiên (jitter ±30 phút) nhằm chống các bộ lọc spam và WAF Cổng Thuế.
        """
        now = current_time or datetime.now()
        today = now.date()
        use_jitter = self._enable_jitter if with_jitter is None else with_jitter
        jitter_sec = self._jitter_minutes * 60

        # Nếu không áp dụng jitter (chế độ kiểm thử xác định hoặc preview)
        if not use_jitter or jitter_sec == 0:
            candidates = [
                datetime(today.year, today.month, today.day, h, 0, 0)
                for h in self.scheduled_hours
                if datetime(today.year, today.month, today.day, h, 0, 0) > now
            ]
            if candidates:
                next_run = candidates[0]
            else:
                tomorrow = today + timedelta(days=1)
                next_run = datetime(
                    tomorrow.year, tomorrow.month, tomorrow.day, self.scheduled_hours[0], 0, 0
                )
            delay_seconds = max(0.0, (next_run - now).total_seconds())
            return next_run, delay_seconds

        # Chế độ áp dụng Jitter ngẫu nhiên (±30 phút)
        slot_candidates: list[tuple[date, int, datetime, datetime, datetime]] = []
        for d in (today, today + timedelta(days=1)):
            for h in self.scheduled_hours:
                base_dt = datetime(d.year, d.month, d.day, h, 0, 0)
                win_start = base_dt - timedelta(seconds=jitter_sec)
                win_end = base_dt + timedelta(seconds=jitter_sec)
                slot_candidates.append((d, h, base_dt, win_start, win_end))

        chosen_slot = None
        for d, h, base_dt, win_start, win_end in slot_candidates:
            if update_state and self._last_scheduled_slot and (d, h) <= self._last_scheduled_slot:
                continue
            if win_end > now:
                chosen_slot = (d, h, base_dt, win_start, win_end)
                break

        if not chosen_slot:
            after_tomorrow = today + timedelta(days=2)
            h = self.scheduled_hours[0]
            base_dt = datetime(
                after_tomorrow.year, after_tomorrow.month, after_tomorrow.day, h, 0, 0
            )
            win_start = base_dt - timedelta(seconds=jitter_sec)
            win_end = base_dt + timedelta(seconds=jitter_sec)
            chosen_slot = (after_tomorrow, h, base_dt, win_start, win_end)

        d, h, base_dt, win_start, win_end = chosen_slot

        if now < win_start:
            offset = random.randint(-jitter_sec, jitter_sec)
            target = base_dt + timedelta(seconds=offset)
        else:
            remaining = max(1.0, (win_end - now).total_seconds())
            min_lead = min(5.0, remaining / 2)
            rand_delta = random.uniform(min_lead, remaining)
            target = now + timedelta(seconds=rand_delta)

        delay_seconds = max(0.0, (target - now).total_seconds())

        if update_state:
            self._last_scheduled_slot = (d, h)

        return target, delay_seconds

    async def execute_scheduled_sync(
        self,
        trigger_slot: str | None = None,
        force: bool = False,
        sync_type: str = "all",
    ) -> dict[str, Any]:
        """Thực thi tiến trình đồng bộ hóa đơn định kỳ và gửi email thông báo khi có lỗi."""
        execution_time = datetime.now()
        slot_label = trigger_slot or format_vn_clock(execution_time)
        logger.info(
            "🚀 [AUTO-SYNC] Bắt đầu phiên đồng bộ hóa đơn tự động định kỳ (Khung giờ: %s)...",
            slot_label,
        )

        history_entry: dict[str, Any] = {
            "execution_time": execution_time.isoformat(),
            "trigger_slot": slot_label,
            "force": force,
            "status": "running",
            "new_imported": 0,
            "duplicates": 0,
            "error_details": None,
            "alert_sent": False,
        }

        # Nếu tiến trình đang tạm ngưng và không phải là force trigger thủ công
        if self._is_paused and not force:
            logger.info(
                "⏸️ [AUTO-SYNC] Bỏ qua phiên đồng bộ %s vì cron job đang TẠM NGƯNG (%s).",
                slot_label,
                self._pause_reason,
            )
            history_entry["status"] = "paused"
            history_entry["error_details"] = self._pause_reason
            self._last_run_time = execution_time
            self._last_run_status = "paused"
            self._execution_history.insert(0, history_entry)
            if len(self._execution_history) > 50:
                self._execution_history = self._execution_history[:50]
            return history_entry

        try:
            # Chạy đồng bộ trong khoảng 30 ngày gần nhất đến hôm nay
            to_date_str = execution_time.strftime("%Y-%m-%d")
            from_date_str = (execution_time - timedelta(days=30)).strftime("%Y-%m-%d")

            sync_res = self._invoice_service.run_gdt_tax_sync(
                from_date=from_date_str,
                to_date=to_date_str,
                sync_type=sync_type,
            )

            status = sync_res.get("status", "error")
            total_new = sync_res.get("total_new", 0)
            total_dup = sync_res.get("total_duplicates", 0)
            connectors = sync_res.get("connectors", [])

            # Xác định các connector bị lỗi nếu có (loại trừ cooldown_active)
            failed_connectors = [
                c for c in connectors if c.get("status") not in ("success", "cooldown_active")
            ]

            is_cooldown = (status == "cooldown_active") or (
                len(connectors) > 0 and all(c.get("status") == "cooldown_active" for c in connectors)
            )

            has_error = (not is_cooldown) and (
                (status not in ("success", "not_configured")) or (len(failed_connectors) > 0)
            )

            history_entry["status"] = "cooldown_active" if is_cooldown else status
            history_entry["new_imported"] = total_new
            history_entry["duplicates"] = total_dup
            history_entry["raw_response"] = sync_res

            self._last_run_time = execution_time
            self._last_run_status = history_entry["status"]

            if is_cooldown:
                cooldown_msg = (
                    sync_res.get("message")
                    or "Đang trong thời gian giãn cách 3 giờ để bảo vệ an toàn tài khoản Cổng Thuế."
                )
                history_entry["error_details"] = cooldown_msg
                logger.info(
                    "⏳ [AUTO-SYNC] Phiên đồng bộ %s tạm bỏ qua (đang kích hoạt cooldown 3h an toàn): %s",
                    slot_label,
                    cooldown_msg,
                )
            elif has_error:
                error_msg = (
                    sync_res.get("message") or "Lỗi kết nối Cổng Thuế / Hòm thư IMAP"
                )
                history_entry["error_details"] = error_msg
                logger.warning(
                    "⚠️ [AUTO-SYNC] Phát hiện lỗi trong phiên đồng bộ %s: %s",
                    slot_label,
                    error_msg,
                )

                # Chỉ đếm connector bị Cổng Thuế TỪ CHỐI đăng nhập thật sự (auth_error).
                # ai_error = AI không đồng thuận captcha -> phiên tự hủy TRƯỚC khi bấm Đăng nhập,
                # không tốn lượt đăng nhập nên KHÔNG được tính vào bộ đếm ngắt mạch
                # (trước 04/10/2026 ai_error bị đếm nhầm -> có thể ngắt mạch oan cả lịch đồng bộ).
                auth_failed_connectors = [
                    c for c in connectors if c.get("status") == "auth_error"
                ]
                # Có danh sách connector -> tin trạng thái chi tiết từng connector (status tổng có thể
                # bị gộp thô); chỉ khi không có connector mới dựa vào status tổng.
                is_auth_error = (
                    bool(auth_failed_connectors) if connectors else (status == "auth_error")
                )

                circuit_breaker_tripped = False
                circuit_breaker_reason = ""

                if is_auth_error:
                    target_list = auth_failed_connectors if auth_failed_connectors else [{"service": "gdt_portal", "tax_code": "0202111150"}]
                    for c in target_list:
                        svc = c.get("service") or "gdt_portal"
                        tc = c.get("tax_code") or "0202111150"
                        failures = 1
                        if hasattr(self._invoice_service, "increment_auth_failures"):
                            try:
                                res_cnt = self._invoice_service.increment_auth_failures(svc, tc)
                                if isinstance(res_cnt, int):
                                    failures = res_cnt
                            except Exception as cnt_err:
                                logger.error("Lỗi khi gọi increment_auth_failures: %s", cnt_err)

                        logger.warning(
                            "⚠️ [AUTO-SYNC] Cảnh báo xác thực thất bại lần thứ %d/%d cho %s (MST: %s).",
                            failures,
                            self._max_consecutive_auth_failures,
                            svc,
                            tc,
                        )
                        if failures >= self._max_consecutive_auth_failures:
                            circuit_breaker_tripped = True
                            circuit_breaker_reason = (
                                f"Hệ thống phát hiện {failures} lần đăng nhập Cổng Thuế thất bại liên tiếp (MST: {tc}). "
                                f"Đã tự động ngắt mạch (Circuit Breaker) và tạm ngưng toàn bộ tiến trình để bảo vệ tài khoản không bị khóa."
                            )
                            if hasattr(self._invoice_service, "deactivate_sync_config"):
                                try:
                                    self._invoice_service.deactivate_sync_config(
                                        service_type=svc,
                                        tax_code=tc,
                                        reason=circuit_breaker_reason,
                                        status="CIRCUIT_BREAKER_SUSPENDED",
                                    )
                                except Exception as deact_err:
                                    logger.error("Lỗi khi gọi deactivate_sync_config: %s", deact_err)

                if circuit_breaker_tripped:
                    self.pause(reason=circuit_breaker_reason)
                    history_entry["circuit_breaker_tripped"] = True
                    history_entry["status"] = "circuit_breaker_suspended"

                    # Gửi email cảnh báo KHẨN CẤP ngắt mạch
                    alert_success = await self.send_circuit_breaker_alert(
                        slot_label=slot_label,
                        error_message=circuit_breaker_reason,
                        connectors=connectors,
                        sync_response=sync_res,
                    )
                    history_entry["alert_sent"] = alert_success
                else:
                    # Gửi email cảnh báo thông thường
                    alert_success = await self.send_sync_failure_alert(
                        slot_label=slot_label,
                        error_message=error_msg,
                        connectors=connectors,
                        sync_response=sync_res,
                    )
                    history_entry["alert_sent"] = alert_success

            else:
                # Đồng bộ thành công -> Reset bộ đếm lỗi xác thực
                if hasattr(self._invoice_service, "reset_auth_failures"):
                    try:
                        self._invoice_service.reset_auth_failures("gdt_portal", "0202111150")
                        for c in connectors:
                            if c.get("service") and c.get("tax_code"):
                                self._invoice_service.reset_auth_failures(c["service"], c["tax_code"])
                    except Exception as rst_err:
                        logger.error("Lỗi khi gọi reset_auth_failures: %s", rst_err)

                logger.info(
                    "✅ [AUTO-SYNC] Phiên đồng bộ %s thành công: %d hóa đơn mới, %d hóa đơn trùng.",
                    slot_label,
                    total_new,
                    total_dup,
                )

        except Exception as exc:
            stack = traceback.format_exc()
            err_str = str(exc)
            logger.error(
                "❌ [AUTO-SYNC] Ngoại lệ bất ngờ khi đồng bộ tự động %s: %s\n%s",
                slot_label,
                err_str,
                stack,
            )

            history_entry["status"] = "exception"
            history_entry["error_details"] = f"{err_str}\n{stack}"
            self._last_run_time = execution_time
            self._last_run_status = "exception"

            alert_success = await self.send_sync_failure_alert(
                slot_label=slot_label,
                error_message=f"Lỗi hệ thống bất ngờ: {err_str}",
                exception=exc,
            )
            history_entry["alert_sent"] = alert_success

        # Lưu tối đa 50 lần chạy gần nhất
        self._execution_history.insert(0, history_entry)
        if len(self._execution_history) > 50:
            self._execution_history = self._execution_history[:50]

        return history_entry

    async def send_sync_failure_alert(
        self,
        slot_label: str,
        error_message: str,
        connectors: list[dict[str, Any]] | None = None,
        sync_response: dict[str, Any] | None = None,
        exception: Exception | None = None,
    ) -> bool:
        """Gửi email cảnh báo sự cố đồng bộ hóa đơn về hòm thư cấu hình (contact@dinhsonconstruction.com)."""
        time_str = format_vn_time()
        stack_trace = traceback.format_exc() if exception else ""

        # Xây dựng bảng hiển thị các connector
        connector_rows = ""
        if connectors:
            for c in connectors:
                srv = str(c.get("service", "Unknown")).upper()
                st = str(c.get("status", "unknown")).upper()
                msg = str(c.get("message", "N/A"))
                color = "#4ade80" if st == "SUCCESS" else "#f87171"
                connector_rows += f"""
                <tr>
                    <td style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #38bdf8; font-weight: bold;">{srv}</td>
                    <td style="padding: 8px 12px; border-bottom: 1px solid #334155; color: {color}; font-weight: bold;">{st}</td>
                    <td style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #cbd5e1; font-size: 12px;">{msg}</td>
                </tr>
                """
        else:
            connector_rows = f"""
            <tr>
                <td colspan="3" style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #f87171;">
                    {error_message}
                </td>
            </tr>
            """

        stack_section = ""
        if stack_trace and stack_trace != "NoneType: None\n":
            stack_section = f"""
            <div style="background-color: #0f172a; padding: 12px; border-radius: 6px; border-left: 4px solid #ef4444; margin-top: 14px;">
                <h4 style="margin: 0 0 6px 0; color: #cbd5e1; font-size: 13px;">Chi tiết lỗi hệ thống (Stack Trace):</h4>
                <pre style="color: #fca5a5; font-size: 11px; overflow-x: auto; white-space: pre-wrap; font-family: Consolas, monospace; margin: 0;">{stack_trace}</pre>
            </div>
            """

        html_content = f"""
        <div style="background-color: #0b0f19; color: #f8fafc; padding: 24px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; border-radius: 8px; max-width: 680px; margin: 0 auto; border: 1px solid #1e293b;">
            <div style="border-bottom: 1px solid #334155; padding-bottom: 14px; margin-bottom: 16px;">
                <h2 style="color: #ef4444; margin: 0; font-size: 18px; display: flex; align-items: center;">
                    🚨 [DSCons ERP Alert] Sự Cố Đồng Bộ Hóa Đơn Định Kỳ ({slot_label})
                </h2>
            </div>
            
            <p style="margin: 4px 0; color: #cbd5e1; font-size: 13px;">
                <strong>Thời điểm ghi nhận:</strong> <span style="color: #f8fafc;">{time_str}</span>
            </p>
            <p style="margin: 4px 0; color: #cbd5e1; font-size: 13px;">
                <strong>Khung giờ chạy tự động:</strong> <span style="background: #1e293b; padding: 2px 8px; border-radius: 4px; color: #38bdf8; font-weight: bold;">{slot_label}</span> (Lịch: {self.scheduled_slots_vn_label()} hàng ngày (GMT+7), biên độ ngẫu nhiên ±30 phút)
            </p>
            <p style="margin: 4px 0; color: #cbd5e1; font-size: 13px;">
                <strong>Mã Số Thuế Đơn Vị:</strong> <span style="color: #fbbf24; font-weight: bold;">0202111150 (Công ty TNHH Xây Dựng Định Sơn)</span>
            </p>

            <div style="margin-top: 16px;">
                <h4 style="margin: 0 0 8px 0; color: #94a3b8; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px;">
                    Chi Tiết Trạng Thái Kết Nối Đồng Bộ
                </h4>
                <table style="width: 100%; border-collapse: collapse; background: #1e293b; border-radius: 6px; overflow: hidden; font-size: 13px;">
                    <thead>
                        <tr style="background: #334155; text-align: left; color: #94a3b8;">
                            <th style="padding: 8px 12px; font-size: 12px;">Dịch vụ</th>
                            <th style="padding: 8px 12px; font-size: 12px;">Trạng thái</th>
                            <th style="padding: 8px 12px; font-size: 12px;">Thông báo</th>
                        </tr>
                    </thead>
                    <tbody>
                        {connector_rows}
                    </tbody>
                </table>
            </div>

            {stack_section}

            <div style="background-color: #1e293b; padding: 14px; border-radius: 6px; margin-top: 18px; border-left: 4px solid #38bdf8;">
                <h4 style="margin: 0 0 6px 0; color: #38bdf8; font-size: 13px;">📌 Khuyến Nghị Hành Động Cho Kế Toán / Quản Trị Viên:</h4>
                <ul style="margin: 0; padding-left: 20px; color: #cbd5e1; font-size: 12px; line-height: 1.6;">
                    <li>Kiểm tra lại thông tin tài khoản, mật khẩu Cổng HĐĐT Tổng Cục Thuế trên hệ thống DSCons ERP.</li>
                    <li>Đảm bảo dịch vụ mạng và hòm thư nhận hóa đơn (IMAP) không bị khóa hoặc đổi mật khẩu ứng dụng.</li>
                    <li>Truy cập mục <strong>Quản lý Hóa Đơn &rarr; Cấu hình Kết nối</strong> trên DSCons ERP để kiểm tra trực tiếp.</li>
                </ul>
            </div>

            <hr style="border: none; border-top: 1px solid #334155; margin: 20px 0 14px 0;" />
            <p style="font-size: 11px; color: #64748b; margin: 0; text-align: center;">
                Hệ Thống Lập Lịch Tự Động & Giám Sát Hóa Đơn - Công Ty TNHH Xây Dựng Định Sơn (MST: 0202111150).
            </p>
        </div>
        """

        subject = f"🚨 [DSCons ERP Alert] Lỗi Đồng Bộ Hóa Đơn Tự Động Định Kỳ ({slot_label}) - {time_str}"

        try:
            res = await BrevoEmailService.send_email(
                recipient_email=self._alert_email,
                subject=subject,
                html_content=html_content,
                recipient_name="Ban Quản Trị & Kế Toán DSCons",
                sender_name="DSCons Auto Invoice Sync Monitor",
            )
            is_success = res.get("success", False)
            if is_success:
                logger.info(
                    "📧 [AUTO-SYNC] Đã gửi email cảnh báo sự cố thành công tới %s",
                    self._alert_email,
                )
            else:
                logger.warning(
                    "⚠️ [AUTO-SYNC] Không thể gửi email cảnh báo qua Brevo: %s",
                    res.get("error"),
                )

            # Gửi thêm Telegram alert nếu có
            try:
                from app.modules.integrations.application.telegram_service import get_telegram_service

                tg = get_telegram_service()
                if tg and tg.is_configured():
                    tg_msg = (
                        f"🚨 *[DSCons ERP - Lỗi Đồng Bộ Hóa Đơn Định Kỳ]*\n"
                        f"⏰ Khung giờ: `{slot_label}` ({time_str})\n"
                        f"⚠️ Chi tiết: `{error_message}`\n"
                        f"📧 Đã gửi báo cáo chi tiết về: `{self._alert_email}`"
                    )
                    await tg.send_message(tg_msg)
            except Exception as tg_err:
                logger.debug("Telegram alert skipped or error: %s", tg_err)

            return is_success
        except Exception as mail_err:
            logger.error("❌ [AUTO-SYNC] Lỗi khi gửi email cảnh báo: %s", mail_err)
            return False

    async def send_circuit_breaker_alert(
        self,
        slot_label: str,
        error_message: str,
        connectors: list[dict[str, Any]] | None = None,
        sync_response: dict[str, Any] | None = None,
    ) -> bool:
        """Gửi email cảnh báo KHẨN CẤP khi cơ chế Circuit Breaker tự động ngắt mạch do >= 2 lần đăng nhập sai liên tiếp."""
        time_str = format_vn_time()

        connector_rows = ""
        if connectors:
            for c in connectors:
                srv = str(c.get("service", "Unknown")).upper()
                st = str(c.get("status", "unknown")).upper()
                msg = str(c.get("message", "N/A"))
                connector_rows += f"""
                <tr>
                    <td style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #38bdf8; font-weight: bold;">{srv}</td>
                    <td style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #f87171; font-weight: bold;">{st}</td>
                    <td style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #cbd5e1; font-size: 12px;">{msg}</td>
                </tr>
                """
        else:
            connector_rows = f"""
            <tr>
                <td colspan="3" style="padding: 8px 12px; border-bottom: 1px solid #334155; color: #f87171;">
                    {error_message}
                </td>
            </tr>
            """

        html_content = f"""
        <div style="background-color: #0b0f19; color: #f8fafc; padding: 24px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; border-radius: 8px; max-width: 680px; margin: 0 auto; border: 2px solid #ef4444;">
            <div style="background-color: #7f1d1d; padding: 14px 18px; border-radius: 6px; margin-bottom: 16px;">
                <h2 style="color: #fef2f2; margin: 0; font-size: 18px;">
                    🚨 [KHẨN CẤP - TỰ ĐỘNG DỪNG] Ngắt Mạch Đồng Bộ Hóa Đơn Cổng Thuế (Circuit Breaker)
                </h2>
                <p style="color: #fecaca; font-size: 13px; margin: 6px 0 0 0;">
                    Hệ thống phát hiện sai thông tin xác thực từ 2 lần liên tiếp. ĐÃ TỰ ĐỘNG DỪNG NGAY TIẾN TRÌNH để bảo vệ tài khoản Cổng Thuế không bị khóa tiếp.
                </p>
            </div>

            <p style="margin: 4px 0; color: #cbd5e1; font-size: 13px;">
                <strong>Thời điểm ngắt mạch:</strong> <span style="color: #f8fafc;">{time_str}</span>
            </p>
            <p style="margin: 4px 0; color: #cbd5e1; font-size: 13px;">
                <strong>Mã Số Thuế:</strong> <span style="color: #fbbf24; font-weight: bold;">0202111150 (Công ty TNHH Xây Dựng Định Sơn)</span>
            </p>
            <p style="margin: 4px 0; color: #cbd5e1; font-size: 13px;">
                <strong>Khung giờ chạy:</strong> <span style="background: #1e293b; padding: 2px 8px; border-radius: 4px; color: #38bdf8; font-weight: bold;">{slot_label}</span>
            </p>

            <div style="background-color: #1e293b; padding: 14px; border-radius: 6px; margin: 16px 0; border-left: 4px solid #ef4444;">
                <h4 style="margin: 0 0 6px 0; color: #f87171; font-size: 13px;">Lý do tự động ngắt mạch an toàn:</h4>
                <p style="margin: 0; color: #fca5a5; font-size: 13px; font-weight: 500;">{error_message}</p>
            </div>

            <div style="margin-top: 16px;">
                <h4 style="margin: 0 0 8px 0; color: #94a3b8; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px;">
                    Chi Tiết Phản Hồi Từ Cổng Dịch Vụ
                </h4>
                <table style="width: 100%; border-collapse: collapse; background: #1e293b; border-radius: 6px; overflow: hidden; font-size: 13px;">
                    <thead>
                        <tr style="background: #334155; text-align: left; color: #94a3b8;">
                            <th style="padding: 8px 12px; font-size: 12px;">Dịch vụ</th>
                            <th style="padding: 8px 12px; font-size: 12px;">Trạng thái</th>
                            <th style="padding: 8px 12px; font-size: 12px;">Thông báo</th>
                        </tr>
                    </thead>
                    <tbody>
                        {connector_rows}
                    </tbody>
                </table>
            </div>

            <div style="background-color: #172554; padding: 14px; border-radius: 6px; margin-top: 18px; border-left: 4px solid #60a5fa;">
                <h4 style="margin: 0 0 6px 0; color: #93c5fd; font-size: 13px;">🛠️ Hướng Dẫn Xử Lý Cho Quản Trị Viên / Kế Toán:</h4>
                <ol style="margin: 0; padding-left: 20px; color: #bfdbfe; font-size: 12px; line-height: 1.6;">
                    <li><strong>Không thử đăng nhập dồn dập</strong> để tránh bị máy chủ Cục Thuế kích hoạt khóa tài khoản.</li>
                    <li>Đăng nhập thủ công vào <a href="https://hoadondientu.gdt.gov.vn" style="color: #38bdf8;" target="_blank">hoadondientu.gdt.gov.vn</a> bằng trình duyệt để xác thực chính xác mật khẩu của MST <strong>0202111150</strong>.</li>
                    <li>Sau khi kiểm tra đúng, truy cập vào giao diện <strong>Hóa Đơn &rarr; Cấu hình kết nối</strong> trên DSCons ERP để cập nhật lại mật khẩu và bấm nút <strong>Khôi phục đồng bộ (Resume)</strong>.</li>
                </ol>
            </div>

            <hr style="border: none; border-top: 1px solid #334155; margin: 20px 0 14px 0;" />
            <p style="font-size: 11px; color: #64748b; margin: 0; text-align: center;">
                Hệ Thống Tự Động Ngắt Mạch Bảo Vệ An Toàn - DSCons ERP (MST: 0202111150).
            </p>
        </div>
        """

        subject = f"🚨 [KHẨN CẤP - TỰ ĐỘNG DỪNG] Ngắt Mạch Đồng Bộ Hóa Đơn: 2 Lần Đăng Nhập Thất Bại Liên Tiếp ({slot_label}) - {time_str}"

        try:
            res = await BrevoEmailService.send_email(
                recipient_email=self._alert_email,
                subject=subject,
                html_content=html_content,
                recipient_name="Ban Quản Trị & Kế Toán DSCons",
                sender_name="DSCons Auto Invoice Sync Monitor",
            )
            is_success = res.get("success", False)
            if is_success:
                logger.info(
                    "📧 [AUTO-SYNC] Đã gửi email cảnh báo ngắt mạch khẩn cấp thành công tới %s",
                    self._alert_email,
                )
            else:
                logger.warning(
                    "⚠️ [AUTO-SYNC] Không thể gửi email cảnh báo ngắt mạch qua Brevo: %s",
                    res.get("error"),
                )

            # Gửi thêm Telegram alert nếu có
            try:
                from app.modules.integrations.application.telegram_service import get_telegram_service

                tg = get_telegram_service()
                if tg and tg.is_configured():
                    tg_msg = (
                        f"🚨 *[DSCons ERP - KHẨN CẤP TỰ ĐỘNG DỪNG ĐỒNG BỘ]*\n"
                        f"⏰ Khung giờ: `{slot_label}` ({time_str})\n"
                        f"🛑 Cơ chế ngắt mạch: Đã phát hiện 2 lần đăng nhập Cổng Thuế thất bại liên tiếp.\n"
                        f"⚠️ Hệ thống đã tự động TẠM NGƯNG tiến trình để bảo vệ tài khoản không bị khóa.\n"
                        f"📧 Đã gửi hướng dẫn chi tiết về: `{self._alert_email}`"
                    )
                    await tg.send_message(tg_msg)
            except Exception as tg_err:
                logger.debug("Telegram alert skipped or error: %s", tg_err)

            return is_success
        except Exception as mail_err:
            logger.error("❌ [AUTO-SYNC] Lỗi khi gửi email cảnh báo ngắt mạch: %s", mail_err)
            return False

    def get_status(self) -> dict[str, Any]:
        """Lấy thông tin trạng thái lập lịch hiện tại và lịch sử các lần đồng bộ."""
        next_run, delay_seconds = self.calculate_next_run(with_jitter=False)
        return {
            "is_running": self._is_running,
            "is_paused": self._is_paused,
            "pause_reason": self._pause_reason,
            "max_consecutive_auth_failures": self._max_consecutive_auth_failures,
            "scheduled_hours": SCHEDULED_HOURS,
            "scheduled_slots": [f"{h:02d}:00" for h in SCHEDULED_HOURS],
            "jitter_minutes": self._jitter_minutes,
            "enable_jitter": self._enable_jitter,
            "alert_recipient_email": self._alert_email,
            "next_run_at": next_run.isoformat() if not self._is_paused else None,
            "seconds_until_next_run": round(delay_seconds, 1) if not self._is_paused else None,
            "last_run_at": self._last_run_time.isoformat()
            if self._last_run_time
            else None,
            "last_run_status": self._last_run_status,
            "history_count": len(self._execution_history),
            "recent_history": self._execution_history[:10],
        }


_invoice_auto_sync_instance: InvoiceAutoSyncSchedulerService | None = None


def get_invoice_auto_sync_service() -> InvoiceAutoSyncSchedulerService:
    """Singleton factory cho InvoiceAutoSyncSchedulerService."""
    global _invoice_auto_sync_instance
    if _invoice_auto_sync_instance is None:
        # Mặc định tạm ngưng cron job tự động đồng bộ khi INVOICE_AUTO_SYNC_ENABLED là false (chờ Cục Thuế mở khóa)
        auto_sync_enabled = os.getenv("INVOICE_AUTO_SYNC_ENABLED", "false").lower() in (
            "true",
            "1",
            "yes",
        )
        is_paused = not auto_sync_enabled
        _invoice_auto_sync_instance = InvoiceAutoSyncSchedulerService(is_paused=is_paused)
    return _invoice_auto_sync_instance


async def start_invoice_auto_sync_scheduler_loop() -> None:
    """Vòng lặp chạy nền của Invoice Auto-Sync Scheduler.

    Tự động kích hoạt các mốc 01:00 và 13:00 mỗi ngày (kèm biên độ ngẫu nhiên ±30 phút để chống spam/WAF).
    """
    service = get_invoice_auto_sync_service()
    service._is_running = True
    if service.is_paused:
        logger.warning(
            "⏸️ [DSCons] Invoice Auto-Sync Scheduler Loop đã khởi động nhưng ở trạng thái TẠM NGƯNG (%s). Các mốc chạy tự động sẽ bỏ qua cho tới khi được khôi phục.",
            service.pause_reason,
        )
    else:
        logger.info(
            "⏰ [DSCons] Invoice Auto-Sync Scheduler Loop đã khởi động. Lịch chạy: %s (GMT+7) hàng ngày (biên độ ngẫu nhiên ±%dp).",
            service.scheduled_slots_vn_label(),
            service.jitter_minutes,
        )

    while True:
        try:
            next_run, delay_seconds = service.calculate_next_run(
                with_jitter=True, update_state=True
            )
            # next_run là giờ hệ thống (UTC trên Cloud) -> hiển thị giờ VN cho khớp dấu thời gian log
            if service.is_paused:
                logger.info(
                    "⏸️ [AUTO-SYNC] Đang tạm ngưng (Lý do: %s). Vòng lặp ngủ đến mốc tiếp theo %s...",
                    service.pause_reason,
                    format_vn_time(next_run),
                )
            else:
                logger.info(
                    "⏳ [AUTO-SYNC] Đang chờ phiên kế tiếp vào lúc %s (còn %d giây, biên độ ±%dp)...",
                    format_vn_time(next_run),
                    int(delay_seconds),
                    service.jitter_minutes,
                )

            # Chờ đến mốc giờ kế tiếp
            await asyncio.sleep(delay_seconds)

            # Kích hoạt phiên đồng bộ tự động
            # Nhãn khung giờ hiển thị theo giờ VN (container chạy UTC: 01:17 UTC -> "08:17")
            trigger_slot = format_vn_clock(next_run)
            await service.execute_scheduled_sync(trigger_slot=trigger_slot)

            # Tránh lặp lại ngay trong cùng 1 giây
            await asyncio.sleep(2)

        except asyncio.CancelledError:
            service._is_running = False
            logger.info(
                "🛑 [AUTO-SYNC] Invoice Auto-Sync Scheduler Loop đã dừng (Cancelled)."
            )
            break
        except Exception as err:
            logger.error("❌ [AUTO-SYNC] Lỗi vòng lặp Scheduler: %s", err)
            await asyncio.sleep(30)
