from __future__ import annotations

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.financial_connector import FinancialConnectorEvent
from app.models.financial_record import ExpenseClaim
from app.schemas.financial_connector import FinancialEventEnvelope
from app.services.expense_claim_tenant_scope import ExpenseClaimTenantScopeMixin
from app.services.financial_connector_actions import FinancialConnectorActionService
from app.services.financial_connector_auth import VerifiedConnectorRequest
from app.services.payment_reconciliation import ReconciliationResult


class FinancialConnectorSimulationService:
    """只计算非生产连接器结果，不创建或修改任何核心财务投影。"""

    def __init__(self, db: Session) -> None:
        self.db = db

    def project(
        self,
        envelope: FinancialEventEnvelope,
        verified: VerifiedConnectorRequest,
    ) -> ReconciliationResult:
        if envelope.event_type in {"payment_settled", "payment_failed"}:
            return self._payment_projection(envelope)
        return self._origin_projection(envelope, verified)

    def _payment_projection(self, envelope: FinancialEventEnvelope) -> ReconciliationResult:
        claim = self._claim(envelope.tenant_id, str(envelope.payload.get("claim_id") or ""))
        if claim is None:
            return self._result(error_code="claim_not_found")
        mismatch = FinancialConnectorActionService.payment_payload_mismatch(
            claim,
            envelope.payload,
        )
        if mismatch is None and envelope.event_type == "payment_settled":
            return self._result(claim=claim)
        return self._result(
            claim=claim,
            error_code=mismatch or "external_payment_failed",
        )

    def _origin_projection(
        self,
        envelope: FinancialEventEnvelope,
        verified: VerifiedConnectorRequest,
    ) -> ReconciliationResult:
        origin = self.db.scalar(
            select(FinancialConnectorEvent).where(
                FinancialConnectorEvent.tenant_id == envelope.tenant_id,
                FinancialConnectorEvent.provider == verified.config.provider,
                FinancialConnectorEvent.environment == verified.config.environment,
                FinancialConnectorEvent.verification_level == verified.verification_level,
                FinancialConnectorEvent.external_event_id
                == str(envelope.payload.get("origin_external_event_id") or ""),
            )
        )
        if origin is None or not origin.claim_id:
            return self._result(error_code="origin_settlement_not_found")
        if origin.event_type != "payment_settled" or origin.processing_status != "processed":
            return self._result(error_code="origin_settlement_not_processed")
        if str(envelope.payload.get("claim_id") or "") != str(origin.claim_id):
            return self._result(error_code="origin_claim_mismatch")
        claim = self._claim(envelope.tenant_id, origin.claim_id)
        if claim is None:
            return self._result(error_code="claim_not_found")
        mismatch = FinancialConnectorActionService.payment_payload_mismatch(
            claim,
            envelope.payload,
            require_external_reference=envelope.event_type
            in {"payment_refunded", "payment_reversed"},
        )
        if mismatch:
            prefix = "erp_" if envelope.event_type.startswith("erp_") else ""
            return self._result(claim=claim, error_code=f"{prefix}{mismatch}")
        if envelope.event_type == "erp_posting_failed":
            return self._result(claim=claim, error_code="erp_posting_failed")
        return self._result(claim=claim)

    def _claim(self, tenant_id: str, claim_id: str) -> ExpenseClaim | None:
        return self.db.scalar(
            select(ExpenseClaim).where(
                ExpenseClaim.id == str(claim_id).strip(),
                ExpenseClaimTenantScopeMixin.build_claim_tenant_condition(tenant_id),
            )
        )

    @staticmethod
    def _result(
        *,
        claim: ExpenseClaim | None = None,
        error_code: str | None = None,
    ) -> ReconciliationResult:
        return ReconciliationResult(
            case=None,
            audit_event=None,
            processing_status="exception" if error_code else "processed",
            error_code=error_code,
            claim_id=claim.id if claim is not None else None,
            expense_case_id=None,
            claim_status=str(claim.status or "") if claim is not None else None,
        )
