from __future__ import annotations

from decimal import Decimal, InvalidOperation
from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.financial_record import ExpenseClaim
from app.services.expense_cases import ExpenseCaseService
from app.services.expense_claim_tenant_scope import ExpenseClaimTenantScopeMixin
from app.services.expense_claims import ExpenseClaimService


class FinancialConnectorActionService:
    """连接器对现有费用状态机的唯一写入口；不负责 commit。"""

    def __init__(self, db: Session) -> None:
        self.db = db
        self.claims = ExpenseClaimService(db)
        self.expense_cases = ExpenseCaseService(db)

    def lock_claim(self, *, tenant_id: str, claim_id: str) -> ExpenseClaim | None:
        statement = select(ExpenseClaim).where(
            ExpenseClaim.id == str(claim_id).strip(),
            ExpenseClaimTenantScopeMixin.build_claim_tenant_condition(tenant_id),
        )
        bind = self.db.get_bind()
        if bind is not None and bind.dialect.name == "postgresql":
            statement = statement.with_for_update()
        return self.db.scalar(statement.execution_options(populate_existing=True))

    @staticmethod
    def settlement_mismatch(claim: ExpenseClaim, payload: dict[str, Any]) -> str | None:
        if str(claim.status or "").strip().lower() != "pending_payment":
            return "claim_not_pending_payment"
        return FinancialConnectorActionService.payment_payload_mismatch(claim, payload)

    @staticmethod
    def payment_payload_mismatch(
        claim: ExpenseClaim,
        payload: dict[str, Any],
        *,
        require_external_reference: bool = True,
    ) -> str | None:
        if str(payload.get("claim_reference") or "").strip() != str(
            claim.claim_no or ""
        ).strip():
            return "claim_reference_mismatch"
        try:
            actual_amount = Decimal(str(payload.get("amount"))).quantize(Decimal("0.01"))
        except (InvalidOperation, TypeError, ValueError):
            return "amount_invalid"
        expected_amount = Decimal(claim.amount or Decimal("0.00")).quantize(Decimal("0.01"))
        if actual_amount != expected_amount:
            return "amount_mismatch"
        if str(payload.get("currency") or "").strip().upper() != str(
            claim.currency or "CNY"
        ).strip().upper():
            return "currency_mismatch"
        if require_external_reference and not str(
            payload.get("external_payment_reference") or ""
        ).strip():
            return "external_reference_missing"
        return None

    def settle_claim(
        self,
        claim: ExpenseClaim,
        *,
        tenant_id: str,
        provider: str,
        connector_event_id: str,
        external_event_id: str,
        content_hash: str,
        verification_level: str,
        evidence_classification: str,
        external_reference_tail: str,
        correlation_id: str,
    ) -> ExpenseClaim:
        return self.claims.mark_claim_paid_from_connector(
            claim,
            tenant_id=tenant_id,
            provider=provider,
            connector_event_id=connector_event_id,
            external_event_id=external_event_id,
            content_hash=content_hash,
            verification_level=verification_level,
            evidence_classification=evidence_classification,
            external_reference_tail=external_reference_tail,
            correlation_id=correlation_id,
        )

    def reopen_claim(
        self,
        claim: ExpenseClaim,
        *,
        tenant_id: str,
        provider: str,
        connector_event_id: str,
        external_event_id: str,
        origin_connector_event_id: str,
        content_hash: str,
        verification_level: str,
        evidence_classification: str,
        correlation_id: str,
    ) -> ExpenseClaim:
        return self.claims.reopen_claim_after_connector_reversal(
            claim,
            tenant_id=tenant_id,
            provider=provider,
            connector_event_id=connector_event_id,
            external_event_id=external_event_id,
            origin_connector_event_id=origin_connector_event_id,
            content_hash=content_hash,
            verification_level=verification_level,
            evidence_classification=evidence_classification,
            correlation_id=correlation_id,
        )

    def record_erp_event(
        self,
        claim: ExpenseClaim,
        *,
        tenant_id: str,
        provider: str,
        connector_event_id: str,
        correlation_id: str,
        event_type: str,
        payload: dict[str, Any],
    ) -> None:
        expense_case = self.expense_cases.ensure_case_for_claim(claim, tenant_id=tenant_id)
        self.expense_cases.record_claim_event(
            claim,
            event_type=event_type,
            actor_id=f"financial-connector:{provider}",
            tenant_id=tenant_id,
            correlation_id=correlation_id,
            idempotency_key=connector_event_id,
            previous_status=str(claim.status or ""),
            previous_approval_stage=str(claim.approval_stage or ""),
            extra_payload={
                "connector_event_id": connector_event_id,
                "origin_external_event_id": str(
                    payload.get("origin_external_event_id") or ""
                ),
                "erp_document_tail": _tail(payload.get("erp_document_number")),
                "accounting_period": str(payload.get("accounting_period") or "")[:24],
            },
            expense_case=expense_case,
            update_case_state=False,
        )


def _tail(value: Any) -> str:
    return str(value or "").strip()[-8:]
