from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.models.expense_case import BusinessEvent
from app.models.financial_record import ExpenseClaim
from app.models.savings import SavingsOpportunity, SavingsRealization
from app.schemas.savings import SavingsEvidenceCreate
from app.services.savings_protocol import savings_payload_fingerprint
from app.services.savings_realization import SavingsRealizationService


class SavingsPaymentReversalService:
    """把可信付款冲回同步成追加式 Savings 纠正事实；不负责 commit。"""

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

    def reconcile(
        self,
        claim: ExpenseClaim,
        payment_reversal_event: BusinessEvent,
        current_user: CurrentUserContext,
    ) -> list[SavingsRealization]:
        tenant_id = str(current_user.tenant_id or "default")
        if payment_reversal_event.tenant_id != tenant_id:
            raise PermissionError("付款冲回事件与 Savings 租户不一致。")
        statement = select(SavingsRealization).where(
            SavingsRealization.tenant_id == tenant_id,
            SavingsRealization.claim_id == claim.id,
            SavingsRealization.realization_type == "actual",
            SavingsRealization.status.in_(("pending_confirmation", "finance_confirmed")),
            SavingsRealization.reversed_at.is_(None),
            SavingsRealization.realization_key.like("payment:%"),
        )
        bind = self.db.get_bind()
        if bind is not None and bind.dialect.name == "postgresql":
            statement = statement.with_for_update()
        realizations = list(self.db.scalars(statement).all())
        corrected: list[SavingsRealization] = []
        for realization in realizations:
            opportunity = self.core._lock_opportunity(
                realization.opportunity_id,
                tenant_id=tenant_id,
            )
            if opportunity is None:
                raise RuntimeError("付款冲回关联的 Savings 机会不存在。")
            corrected.append(
                self._correct_one(
                    opportunity,
                    realization,
                    payment_reversal_event=payment_reversal_event,
                    current_user=current_user,
                )
            )
        self.db.flush()
        return corrected

    def _correct_one(
        self,
        opportunity: SavingsOpportunity,
        realization: SavingsRealization,
        *,
        payment_reversal_event: BusinessEvent,
        current_user: CurrentUserContext,
    ) -> SavingsRealization:
        now = payment_reversal_event.occurred_at or datetime.now(UTC)
        actor_id = current_user.username
        actor_name = current_user.name or current_user.username
        prior_realization_version = realization.version
        prior_opportunity_version = opportunity.version
        before = self.core._realization_state(realization)
        comment = "外部付款已退款或冲回，原现金节省结果同步失效。"
        if realization.status == "finance_confirmed":
            result = self.core._reverse(
                opportunity,
                realization,
                actor_id=actor_id,
                actor_name=actor_name,
                amount=Decimal(realization.actual_net),
                comment=comment,
                now=now,
            )
            action = "reverse"
            business_event_type = "saving_reversed"
        else:
            realization.status = "rejected"
            realization.dedupe_status = "excluded"
            realization.rejected_by_id = actor_id
            realization.rejected_by_name = actor_name
            realization.rejected_at = now
            realization.rejection_reason = comment
            # 尚未进入财务确认口径的结果没有“已核实”事实，直接拒绝而不是伪造 reversed。
            opportunity.status = "rejected"
            opportunity.closed_at = now
            opportunity.version += 1
            opportunity.updated_at = now
            result = realization
            action = "reject"
            business_event_type = "saving_result_rejected"

        evidence = self._reversal_evidence(result, payment_reversal_event, now=now)
        self.core._add_evidence(
            result,
            [evidence],
            actor_id=actor_id,
            verified=evidence.metadata_json.get("evidence_level") == "external_cash",
            now=now,
        )
        realization.version += 1
        realization.updated_at = now
        request_id = f"payment-reversal:{payment_reversal_event.id}:{realization.id}"[:120]
        fingerprint = savings_payload_fingerprint(
            action=action,
            aggregate_type="realization",
            aggregate_id=realization.id,
            actor_id=actor_id,
            payload={"payment_reversal_event_id": payment_reversal_event.id},
        )
        event = self.core._new_realization_event(
            realization,
            action=action,
            request_id=request_id,
            expected_version=prior_realization_version,
            actor_id=actor_id,
            actor_name=actor_name,
            fingerprint=fingerprint,
            payload_json={"payment_reversal_event_id": payment_reversal_event.id},
            before_json=before,
            now=now,
            causation_id=payment_reversal_event.id,
        )
        response = self.core._mutation_response(result, opportunity, event, current_user)
        event.response_json = response.model_dump(mode="json")
        self.db.add(event)
        self.core._add_opportunity_projection_event(
            opportunity,
            action="payment_reversed",
            request_id=request_id,
            actor_id=actor_id,
            prior_version=prior_opportunity_version,
            causation_id=event.id,
            now=now,
        )
        if result.id != realization.id:
            self.core._add_realization_projection_event(
                result,
                request_id=request_id,
                causation_id=event.id,
                now=now,
            )
        self.core._append_business_event(
            opportunity,
            result,
            event_type=business_event_type,
            actor_id=actor_id,
            idempotency_key=request_id,
            correlation_id=payment_reversal_event.correlation_id,
            causation_id=payment_reversal_event.id,
            payload={"source": "external_payment_reversal"},
        )
        return result

    @staticmethod
    def _reversal_evidence(
        realization: SavingsRealization,
        event: BusinessEvent,
        *,
        now: datetime,
    ) -> SavingsEvidenceCreate:
        payload = dict(event.payload_json or {})
        connector_event_id = str(payload.get("connector_event_id") or "").strip()
        classification = str(
            payload.get("evidence_classification") or "external_cash"
        ).strip()
        return SavingsEvidenceCreate(
            evidence_key=f"payment-reversal:{connector_event_id}:{realization.id}",
            evidence_role="external_payment_reversal",
            resource_type="financial_connector_event",
            resource_id=connector_event_id,
            source_system=str(payload.get("provider") or "financial-connector"),
            external_event_id=str(payload.get("external_event_id") or "") or None,
            content_hash=str(payload.get("content_hash") or ""),
            occurred_at=now,
            verification_status=(
                "unavailable" if classification == "simulated_connector" else "unverified"
            ),
            metadata_json={
                "evidence_level": classification,
                "verification_level": payload.get("verification_level"),
                "origin_connector_event_id": payload.get("origin_connector_event_id"),
            },
        )
