"""Application Service for Smart Debt Clearing and Automated Reconciliation."""

from __future__ import annotations

import logging
from datetime import date
from decimal import Decimal
from typing import Any
from uuid import UUID, uuid4

from app.modules.financial.domain.clearing_rules import (
    calculate_settlement_allocation,
    classify_non_invoice_transaction,
    find_subset_sum_invoices,
    match_by_exact_amount,
    match_by_invoice_number,
    match_partner_keywords,
)
from app.modules.financial.domain.settlement_entities import (
    ClearingMethod,
    ClearingResult,
    DebtSettlement,
    SettlementStatus,
)
from app.modules.financial.domain.ports.financial_repository_port import (
    SettlementRepositoryPort,
)


logger = logging.getLogger("dscons.financial.smart_clearing_service")


class SmartClearingService:
    """Orchestrates automated debt matching, settlement allocation, and ledger updates."""

    def __init__(self, repository: SettlementRepositoryPort | None = None) -> None:
        if repository is None:
            from app.modules.financial.infrastructure.settlement_repository import (
                PostgresSettlementRepository,
            )
            repository = PostgresSettlementRepository()
        self._repo = repository

    def reconcile_single_transaction(
        self,
        transaction: dict[str, Any] | UUID,
        dry_run: bool = False,
        min_confidence: float = 0.8,
        cached_unpaid_invoices: list[dict[str, Any]] | None = None,
    ) -> list[DebtSettlement]:
        """Tự động so khớp và cấn trừ cho một giao dịch ngân hàng cụ thể."""
        tx = self._resolve_transaction(transaction)
        if not tx:
            return []

        tx_id = UUID(str(tx["id"]))
        company_id = UUID(str(tx.get("company_id") or uuid4()))
        tx_date = tx.get("transaction_date")
        if hasattr(tx_date, "date"):
            settle_date = tx_date.date()
        elif isinstance(tx_date, str):
            settle_date = date.fromisoformat(tx_date[:10])
        else:
            settle_date = date.today()

        desc = (tx.get("description") or "").strip()
        direction = tx.get("direction", "outflow")
        tx_amount = Decimal(str(tx.get("amount") or 0))
        if tx_amount <= Decimal("0"):
            return []

        # 1. Nhận diện các khoản thanh toán vay / nợ VPBank
        if direction == "outflow" and any(
            k in desc.upper()
            for k in ["LD25", "LD26", "THANH TOAN NO GOC", "THANH TOAN LAI", "TRICH NO TU DONG", "VPBANK"]
        ):
            account_code = "635" if "LAI" in desc.upper() else "341"
            settlement = DebtSettlement(
                company_id=company_id,
                bank_transaction_id=tx_id,
                invoice_id=None,
                partner_name="Ngân hàng TMCP Việt Nam Thịnh Vượng (VPBank)",
                partner_tax_code="0100233583",
                account_code=account_code,
                settlement_amount_vnd=tx_amount,
                settlement_date=settle_date,
                clearing_method=ClearingMethod.VPBANK_LOAN,
                confidence_score=Decimal("0.9800"),
                status=SettlementStatus.SETTLED,
                notes=f"Tự động cấn trừ nghĩa vụ nợ VPBank: {desc[:100]}",
            )
            if not dry_run:
                self._repo.save_settlement(settlement)
            return [settlement]

        # 2. Nhận diện các bút toán phi hóa đơn chuẩn VAS (Phí, Lãi, Thuế, BHXH, Lương)
        non_inv = classify_non_invoice_transaction(
            description=desc,
            direction=direction,
            amount=tx_amount,
        )
        # Các khoản nghiệp vụ thuần tuý không có hóa đơn GTGT (642, 515, 333, 3383, 334)
        if non_inv.matched and non_inv.account_code in ("642", "515", "333", "3383", "334"):
            settle = DebtSettlement(
                company_id=company_id,
                bank_transaction_id=tx_id,
                invoice_id=None,
                partner_name=non_inv.partner_name,
                partner_tax_code=None,
                account_code=non_inv.account_code,
                settlement_amount_vnd=tx_amount,
                settlement_date=settle_date,
                clearing_method=ClearingMethod(non_inv.clearing_method),
                confidence_score=non_inv.confidence_score,
                status=SettlementStatus.SETTLED,
                notes=non_inv.notes,
            )
            if not dry_run:
                self._repo.save_settlement(settle)
            return [settle]

        # 3. Định hướng loại hóa đơn cần so khớp
        # Tiền chi ra (outflow) -> Hóa đơn mua vào (input) của Nhà cung cấp (TK 331)
        # Tiền thu vào (inflow) -> Hóa đơn bán ra (output) cho Khách hàng (TK 131)
        target_inv_dir = "input" if direction == "outflow" else "output"
        account_code = "331" if direction == "outflow" else "131"
        if cached_unpaid_invoices is not None:
            candidate_invoices = [
                i
                for i in cached_unpaid_invoices
                if i.get("direction") == target_inv_dir
                and Decimal(str(i.get("remaining_amount_vnd") or 0)) > Decimal("0")
            ]
        else:
            candidate_invoices = self._repo.get_unpaid_invoices(direction=target_inv_dir)

        settlements_created: list[DebtSettlement] = []

        # --- PASS 1: Khớp chính xác Số Hóa Đơn trong nội dung chuyển khoản (Confidence: 1.0) ---
        for inv in candidate_invoices:
            inv_no = str(inv.get("invoice_number") or "")
            if not inv_no:
                continue

            if match_by_invoice_number(desc, inv_no):
                inv_id = UUID(str(inv["id"]))
                inv_rem = Decimal(str(inv.get("remaining_amount_vnd") or 0))
                partner_name = inv.get("seller_name") if target_inv_dir in ("input", "inbound") else inv.get("buyer_name")
                partner_tax = inv.get("seller_tax_code") if target_inv_dir in ("input", "inbound") else inv.get("buyer_tax_code")

                settled_amt, _, _ = calculate_settlement_allocation(tx_amount, inv_rem)
                if settled_amt > Decimal("0"):
                    settle = DebtSettlement(
                        company_id=company_id,
                        bank_transaction_id=tx_id,
                        invoice_id=inv_id,
                        partner_name=partner_name,
                        partner_tax_code=partner_tax,
                        account_code=account_code,
                        settlement_amount_vnd=settled_amt,
                        settlement_date=settle_date,
                        clearing_method=ClearingMethod.EXACT_INVOICE_NO,
                        confidence_score=Decimal("1.0000"),
                        status=SettlementStatus.SETTLED,
                        notes=f"Khớp chính xác số hóa đơn #{inv_no} trong nội dung: '{desc[:80]}'",
                    )
                    if not dry_run:
                        self._repo.save_settlement(settle)
                        self._repo.update_invoice_settlement(inv_id, settled_amt)
                        self._repo.link_bank_transaction_invoice(tx_id, inv_id)

                    settlements_created.append(settle)
                    return settlements_created

        # --- PASS 2: Khớp chính xác Số Tiền + Tên/MST Đối Tác (Confidence: 0.95) ---
        for inv in candidate_invoices:
            inv_rem = Decimal(str(inv.get("remaining_amount_vnd") or 0))
            if not match_by_exact_amount(tx_amount, inv_rem):
                continue

            partner_name = inv.get("seller_name") if target_inv_dir in ("input", "inbound") else inv.get("buyer_name")
            partner_tax = inv.get("seller_tax_code") if target_inv_dir in ("input", "inbound") else inv.get("buyer_tax_code")

            is_partner_match, p_score = match_partner_keywords(desc, partner_name or "", partner_tax)
            if is_partner_match:
                inv_id = UUID(str(inv["id"]))
                settled_amt, _, _ = calculate_settlement_allocation(tx_amount, inv_rem)
                settle = DebtSettlement(
                    company_id=company_id,
                    bank_transaction_id=tx_id,
                    invoice_id=inv_id,
                    partner_name=partner_name,
                    partner_tax_code=partner_tax,
                    account_code=account_code,
                    settlement_amount_vnd=settled_amt,
                    settlement_date=settle_date,
                    clearing_method=ClearingMethod.EXACT_AMOUNT_PARTNER,
                    confidence_score=Decimal(str(round(0.95 * p_score, 4))),
                    status=SettlementStatus.SETTLED,
                    notes=f"Khớp chính xác số tiền {tx_amount:,.0f} đ với hóa đơn #{inv.get('invoice_number')} và đối tác {partner_name}",
                )
                if not dry_run:
                    self._repo.save_settlement(settle)
                    self._repo.update_invoice_settlement(inv_id, settled_amt)
                    self._repo.link_bank_transaction_invoice(tx_id, inv_id)

                settlements_created.append(settle)
                return settlements_created

        # Tìm danh sách hóa đơn của đối tác được nhắc đến trong mô tả (dùng cho Pass 3 & Pass 5)
        partner_matched_invoices: list[dict[str, Any]] = []
        matched_partner_name: str | None = None
        matched_partner_tax: str | None = None
        for inv in candidate_invoices:
            p_name = inv.get("seller_name") if target_inv_dir in ("input", "inbound") else inv.get("buyer_name")
            p_tax = inv.get("seller_tax_code") if target_inv_dir in ("input", "inbound") else inv.get("buyer_tax_code")
            is_partner_match, p_score = match_partner_keywords(desc, p_name or "", p_tax)
            if is_partner_match and p_score >= 0.6:
                partner_matched_invoices.append(inv)
                if not matched_partner_name:
                    matched_partner_name = p_name
                    matched_partner_tax = p_tax

        # --- PASS 3: Khớp gộp nhiều hóa đơn của cùng 1 đối tác (Subset-Sum Matching, Confidence: 0.95) ---
        if partner_matched_invoices:
            combo = find_subset_sum_invoices(tx_amount, partner_matched_invoices, max_combo=4)
            if combo:
                for item in combo:
                    inv_id = UUID(str(item["id"]))
                    inv_rem = Decimal(str(item.get("remaining_amount_vnd") or 0))
                    settle = DebtSettlement(
                        company_id=company_id,
                        bank_transaction_id=tx_id,
                        invoice_id=inv_id,
                        partner_name=matched_partner_name or item.get("seller_name") or item.get("buyer_name"),
                        partner_tax_code=matched_partner_tax or item.get("seller_tax_code") or item.get("buyer_tax_code"),
                        account_code=account_code,
                        settlement_amount_vnd=inv_rem,
                        settlement_date=settle_date,
                        clearing_method=ClearingMethod.SUBSET_SUM_INVOICES,
                        confidence_score=Decimal("0.9500"),
                        status=SettlementStatus.SETTLED,
                        notes=f"Khớp gộp {len(combo)} hóa đơn (Subset-Sum): HĐ #{item.get('invoice_number')} ({inv_rem:,.0f} đ)",
                    )
                    if not dry_run:
                        self._repo.save_settlement(settle)
                        self._repo.update_invoice_settlement(inv_id, inv_rem)
                        self._repo.link_bank_transaction_invoice(tx_id, inv_id)

                    settlements_created.append(settle)
                return settlements_created

        # --- PASS 4: Bút toán phi hóa đơn khác (Thu CĐT / Đặt cọc tạm ứng NCC) ---
        if non_inv.matched:
            settle = DebtSettlement(
                company_id=company_id,
                bank_transaction_id=tx_id,
                invoice_id=None,
                partner_name=non_inv.partner_name,
                partner_tax_code=None,
                account_code=non_inv.account_code,
                settlement_amount_vnd=tx_amount,
                settlement_date=settle_date,
                clearing_method=ClearingMethod(non_inv.clearing_method),
                confidence_score=non_inv.confidence_score,
                status=SettlementStatus.SETTLED,
                notes=non_inv.notes,
            )
            if not dry_run:
                self._repo.save_settlement(settle)
            return [settle]

        # --- PASS 5: Phân Bổ FIFO Theo Đối Tác Khớp Tên (Confidence: 0.85) ---
        if partner_matched_invoices:
            remaining_tx_amt = tx_amount
            for inv in partner_matched_invoices:
                inv_rem = Decimal(str(inv.get("remaining_amount_vnd") or 0))
                if inv_rem <= Decimal("0"):
                    continue

                settled_amt, _, _ = calculate_settlement_allocation(remaining_tx_amt, inv_rem)
                if settled_amt <= Decimal("0"):
                    break

                inv_id = UUID(str(inv["id"]))
                settle = DebtSettlement(
                    company_id=company_id,
                    bank_transaction_id=tx_id,
                    invoice_id=inv_id,
                    partner_name=matched_partner_name,
                    partner_tax_code=matched_partner_tax,
                    account_code=account_code,
                    settlement_amount_vnd=settled_amt,
                    settlement_date=settle_date,
                    clearing_method=ClearingMethod.FIFO_PARTNER,
                    confidence_score=Decimal("0.8500"),
                    status=SettlementStatus.SETTLED,
                    notes=f"Khớp FIFO đối tác {matched_partner_name} vào hóa đơn #{inv.get('invoice_number')}",
                )
                if not dry_run:
                    self._repo.save_settlement(settle)
                    self._repo.update_invoice_settlement(inv_id, settled_amt)
                    self._repo.link_bank_transaction_invoice(tx_id, inv_id)

                settlements_created.append(settle)
                remaining_tx_amt -= settled_amt
                if remaining_tx_amt <= Decimal("0"):
                    break

        return settlements_created

    def batch_auto_clearing(
        self,
        dry_run: bool = False,
        min_confidence: float = 0.8,
        limit: int = 300,
    ) -> ClearingResult:
        """Quét tự động toàn bộ giao dịch chưa gạch nợ và đối chiếu hàng loạt."""
        unreconciled_txs = self._repo.get_unreconciled_transactions(limit=limit)
        cached_invoices = self._repo.get_unpaid_invoices(
            direction="input"
        ) + self._repo.get_unpaid_invoices(direction="output")
        result = ClearingResult(
            total_transactions_scanned=len(unreconciled_txs),
            dry_run=dry_run,
        )

        all_settlements: list[DebtSettlement] = []
        settled_inv_ids: set[UUID] = set()
        partially_inv_ids: set[UUID] = set()

        for tx in unreconciled_txs:
            created = self.reconcile_single_transaction(
                transaction=tx,
                dry_run=dry_run,
                min_confidence=min_confidence,
                cached_unpaid_invoices=cached_invoices,
            )
            if created:
                result.matched_count += 1
                for s in created:
                    all_settlements.append(s)
                    result.total_cleared_amount_vnd += s.settlement_amount_vnd
                    if s.invoice_id:
                        settled_inv_ids.add(s.invoice_id)

        result.invoices_settled_count = len(settled_inv_ids)
        result.settlements = all_settlements

        logger.info(
            "Batch Auto-Clearing completed (dry_run=%s): scanned %d txs, matched %d txs, total cleared %s VND",
            dry_run,
            result.total_transactions_scanned,
            result.matched_count,
            f"{result.total_cleared_amount_vnd:,.0f}",
        )
        return result

    def manual_clear(
        self,
        transaction_id: UUID,
        invoice_id: UUID,
        settlement_amount: Decimal,
        notes: str = "",
        user_email: str = "accountant@dscons.vn",
    ) -> DebtSettlement:
        """Kế toán thực hiện gạch nợ thủ công."""
        tx = self._resolve_transaction(transaction_id)
        if not tx:
            raise ValueError(f"Giao dịch ngân hàng {transaction_id} không tồn tại.")

        tx_date = tx.get("transaction_date")
        settle_date = tx_date.date() if hasattr(tx_date, "date") else date.today()
        direction = tx.get("direction", "outflow")
        account_code = "331" if direction == "outflow" else "131"

        settle = DebtSettlement(
            company_id=UUID(str(tx.get("company_id") or uuid4())),
            bank_transaction_id=transaction_id,
            invoice_id=invoice_id,
            account_code=account_code,
            settlement_amount_vnd=settlement_amount,
            settlement_date=settle_date,
            clearing_method=ClearingMethod.MANUAL,
            confidence_score=Decimal("1.0000"),
            status=SettlementStatus.SETTLED,
            notes=f"Gạch nợ thủ công bởi {user_email}. {notes}".strip(),
        )

        self._repo.save_settlement(settle)
        self._repo.update_invoice_settlement(invoice_id, settlement_amount)
        self._repo.link_bank_transaction_invoice(transaction_id, invoice_id)
        return settle

    def reverse_settlement(self, settlement_id: UUID) -> dict[str, Any]:
        """Hủy bút toán gạch nợ và hoàn trả số dư nợ hóa đơn."""
        return self._repo.reverse_settlement(settlement_id)

    def get_clearing_summary(self) -> dict[str, Any]:
        """Báo cáo tổng hợp số liệu gạch nợ."""
        return self._repo.get_clearing_summary()

    def list_settlements(
        self,
        status: str | None = None,
        partner_tax_code: str | None = None,
        limit: int = 100,
        offset: int = 0,
    ) -> list[dict[str, Any]]:
        """Lấy danh sách các giao dịch gạch nợ."""
        return self._repo.get_settlements(
            status=status,
            partner_tax_code=partner_tax_code,
            limit=limit,
            offset=offset,
        )

    def _resolve_transaction(
        self, transaction: dict[str, Any] | UUID
    ) -> dict[str, Any] | None:
        """Chuẩn hóa transaction input thành dictionary."""
        if isinstance(transaction, dict):
            return transaction

        # Tru vấn từ CSDL nếu truyền vào UUID
        get_conn = self._repo._get_conn()
        with get_conn() as conn:
            with conn.cursor() as cur:
                cur.execute(
                    """
                    SELECT id, company_id, bank_name, account_number, transaction_date,
                           reference_number, direction, amount, counterparty_name,
                           description, transaction_category, matched_invoice_id
                    FROM erp_bank_transactions
                    WHERE id = %s;
                    """,
                    (str(transaction),),
                )
                row = cur.fetchone()
                return dict(row) if row else None
