from __future__ import annotations

import threading
import uuid
from concurrent.futures import ThreadPoolExecutor

import pytest
from savings_postgres_testkit import (
    _pg_factory_fixture,  # noqa: F401 - 注册名为 pg_factory 的 pytest fixture
    _seed_opportunity,
    _seed_payment_case,
    _seed_pending_realization,
    _SeededRealization,
    _user,
)
from sqlalchemy import func, select, text
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm import Session, sessionmaker

from app.models.expense_case import BusinessEvent
from app.models.financial_record import ExpenseClaim
from app.models.savings import (
    SavingsEvent,
    SavingsEvidenceLink,
    SavingsOpportunity,
    SavingsRealization,
)
from app.schemas.savings import (
    SavingsOpportunityActionCreate,
    SavingsRealizationActionCreate,
)
from app.services.savings_access_policy import SavingsPermissionError
from app.services.savings_actions import SavingsActionService
from app.services.savings_realization import (
    SavingsRealizationError,
    SavingsRealizationService,
)


def test_concurrent_identical_request_replays_one_immutable_event(
    pg_factory: sessionmaker[Session],
) -> None:
    seed = _seed_opportunity(pg_factory, status="identified", owner_id="owner-idempotent")
    actor = _user("owner-idempotent", seed.tenant_id, employee_id="owner-idempotent")
    payload = SavingsOpportunityActionCreate(
        action="accept",
        request_id=f"accept-concurrent-{seed.suffix}",
        expected_version=1,
        comment="并发接受同一个节省机会",
    )
    ready = threading.Barrier(2)

    def accept_once():
        with pg_factory() as db:
            ready.wait(timeout=5)
            return SavingsActionService(db).execute(seed.opportunity_id, payload, actor).response

    with ThreadPoolExecutor(max_workers=2) as pool:
        results = [
            future.result(timeout=10)
            for future in (pool.submit(accept_once), pool.submit(accept_once))
        ]

    assert sorted(item.replayed for item in results) == [False, True]
    assert results[0].opportunity.model_dump(mode="json") == results[1].opportunity.model_dump(
        mode="json"
    )
    with pg_factory() as db:
        opportunity = db.get(SavingsOpportunity, seed.opportunity_id)
        assert opportunity is not None and opportunity.status == "accepted"
        assert opportunity.version == 2
        assert (
            db.scalar(
                select(func.count())
                .select_from(SavingsEvent)
                .where(
                    SavingsEvent.tenant_id == seed.tenant_id,
                    SavingsEvent.actor_id == "owner-idempotent",
                    SavingsEvent.request_id == payload.request_id,
                )
            )
            == 1
        )


def test_same_benefit_concurrent_confirmation_has_one_canonical_winner(
    pg_factory: sessionmaker[Session],
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    benefit_key = f"benefit-shared-{uuid.uuid4().hex}"
    first = _seed_pending_realization(
        pg_factory,
        benefit_key=benefit_key,
        owner_id="owner-canonical-a",
        recorder_id="payer-canonical-a",
    )
    second = _seed_pending_realization(
        pg_factory,
        benefit_key=benefit_key,
        owner_id="owner-canonical-b",
        recorder_id="payer-canonical-b",
    )
    ready = threading.Barrier(2)
    original_confirm = SavingsRealizationService._confirm

    def confirm_after_duplicate_probe(self, *args, **kwargs):
        original_confirm(self, *args, **kwargs)
        ready.wait(timeout=5)

    monkeypatch.setattr(SavingsRealizationService, "_confirm", confirm_after_duplicate_probe)

    def confirm_once(seed: _SeededRealization, actor_id: str):
        payload = SavingsRealizationActionCreate(
            action="confirm",
            request_id=f"confirm-race-{seed.suffix}",
            expected_version=1,
            comment="并发确认相同经济收益",
        )
        with pg_factory() as db:
            try:
                return (
                    SavingsRealizationService(db)
                    .execute_action(
                        seed.realization_id,
                        payload,
                        _user(actor_id, seed.tenant_id, employee_id=actor_id, roles=["finance"]),
                    )
                    .response.realization.id
                )
            except SavingsRealizationError as error:
                return error

    with ThreadPoolExecutor(max_workers=2) as pool:
        outcomes = [
            future.result(timeout=10)
            for future in (
                pool.submit(confirm_once, first, "finance-canonical-a"),
                pool.submit(confirm_once, second, "finance-canonical-b"),
            )
        ]

    assert sum(isinstance(item, str) for item in outcomes) == 1
    assert sum(isinstance(item, SavingsRealizationError) for item in outcomes) == 1
    with pg_factory() as db:
        realizations = list(
            db.scalars(
                select(SavingsRealization).where(
                    SavingsRealization.tenant_id == first.tenant_id,
                    SavingsRealization.benefit_key == benefit_key,
                )
            )
        )
        assert sorted((row.status, row.dedupe_status) for row in realizations) == [
            ("finance_confirmed", "canonical"),
            ("pending_confirmation", "pending_review"),
        ]
        winner = next(row for row in realizations if row.status == "finance_confirmed")
        loser = next(row for row in realizations if row.status == "pending_confirmation")
        loser_evidence = list(
            db.scalars(
                select(SavingsEvidenceLink).where(
                    SavingsEvidenceLink.realization_id == loser.id,
                )
            )
        )
        assert loser_evidence and {item.verification_status for item in loser_evidence} == {
            "unverified"
        }
        assert (
            db.scalar(
                select(func.count())
                .select_from(SavingsEvent)
                .where(
                    SavingsEvent.realization_id.in_([first.realization_id, second.realization_id]),
                    SavingsEvent.action == "confirm",
                )
            )
            == 1
        )
        assert (
            db.scalar(
                select(func.count())
                .select_from(BusinessEvent)
                .where(
                    BusinessEvent.aggregate_type == "savings_realization",
                    BusinessEvent.aggregate_id == winner.id,
                    BusinessEvent.event_type == "saving_confirmed",
                )
            )
            == 1
        )
        assert (
            db.scalar(
                select(func.count())
                .select_from(SavingsRealization)
                .where(
                    SavingsRealization.tenant_id == first.tenant_id,
                    SavingsRealization.benefit_key == benefit_key,
                    SavingsRealization.realization_type == "actual",
                    SavingsRealization.dedupe_status == "canonical",
                    SavingsRealization.status == "finance_confirmed",
                )
            )
            == 1
        )


def test_cross_tenant_action_is_rejected_without_side_effect(
    pg_factory: sessionmaker[Session],
) -> None:
    seed = _seed_opportunity(
        pg_factory, tenant_id="tenant-savings-private", owner_id="owner-private"
    )
    realization_seed = _seed_pending_realization(
        pg_factory,
        tenant_id="tenant-savings-private",
        owner_id="owner-private-realization",
        recorder_id="payer-private-realization",
    )
    request_id = f"cross-tenant-{seed.suffix}"
    realization_request_id = f"cross-tenant-confirm-{realization_seed.suffix}"
    with pg_factory() as db:
        with pytest.raises(LookupError, match="不存在"):
            SavingsActionService(db).execute(
                seed.opportunity_id,
                SavingsOpportunityActionCreate(
                    action="accept",
                    request_id=request_id,
                    expected_version=1,
                    comment="尝试跨租户改变机会",
                ),
                _user("finance-outsider", "tenant-savings-outsider", roles=["finance"]),
            )
        with pytest.raises(LookupError, match="不存在"):
            SavingsRealizationService(db).execute_action(
                realization_seed.realization_id,
                SavingsRealizationActionCreate(
                    action="confirm",
                    request_id=realization_request_id,
                    expected_version=1,
                    comment="尝试跨租户确认实际节省",
                ),
                _user("finance-outsider", "tenant-savings-outsider", roles=["finance"]),
            )

    with pg_factory() as db:
        opportunity = db.get(SavingsOpportunity, seed.opportunity_id)
        assert opportunity is not None and opportunity.status == "identified"
        assert opportunity.version == 1
        realization = db.get(SavingsRealization, realization_seed.realization_id)
        assert realization is not None and realization.status == "pending_confirmation"
        assert realization.version == 1
        assert (
            db.scalar(
                select(func.count())
                .select_from(SavingsEvent)
                .where(SavingsEvent.request_id.in_([request_id, realization_request_id]))
            )
            == 0
        )


def test_confirmation_requires_actor_independent_from_owner_and_recorder(
    pg_factory: sessionmaker[Session],
) -> None:
    seed = _seed_pending_realization(
        pg_factory,
        owner_id="finance-owner-independent",
        recorder_id="finance-recorder-independent",
    )

    for actor_id in ("finance-owner-independent", "finance-recorder-independent"):
        with pg_factory() as db:
            with pytest.raises(SavingsPermissionError, match="不能确认自己"):
                SavingsRealizationService(db).execute_action(
                    seed.realization_id,
                    SavingsRealizationActionCreate(
                        action="confirm",
                        request_id=f"self-confirm-{actor_id}-{seed.suffix}",
                        expected_version=1,
                        comment="不应允许自我确认",
                    ),
                    _user(actor_id, seed.tenant_id, employee_id=actor_id, roles=["finance"]),
                )

    confirmer_id = "finance-independent-reviewer"
    with pg_factory() as db:
        result = SavingsRealizationService(db).execute_action(
            seed.realization_id,
            SavingsRealizationActionCreate(
                action="confirm",
                request_id=f"independent-confirm-{seed.suffix}",
                expected_version=1,
                comment="独立复核付款与政策证据",
            ),
            _user(confirmer_id, seed.tenant_id, employee_id=confirmer_id, roles=["finance"]),
        )
        assert result.response.realization.status == "finance_confirmed"
        assert result.response.realization.finance_confirmer_id == confirmer_id
        assert result.response.realization.recorded_by_id != confirmer_id
        evidence_statuses = set(
            db.scalars(
                select(SavingsEvidenceLink.verification_status).where(
                    SavingsEvidenceLink.realization_id == seed.realization_id,
                )
            )
        )
        assert evidence_statuses == {"verified"}


def test_concurrent_same_payment_event_persists_one_realization(
    pg_factory: sessionmaker[Session],
) -> None:
    seed = _seed_payment_case(pg_factory)
    ready = threading.Barrier(2)

    def realize_once():
        with pg_factory() as db:
            claim = db.get(ExpenseClaim, seed.claim_id)
            event = db.get(BusinessEvent, seed.payment_event_id)
            assert claim is not None and event is not None
            ready.wait(timeout=5)
            created = SavingsRealizationService(db).realize_paid_claim(
                claim,
                event,
                _user(
                    "payment-actor", seed.tenant_id, employee_id="payment-actor", roles=["finance"]
                ),
            )
            db.commit()
            return len(created)

    with ThreadPoolExecutor(max_workers=2) as pool:
        outcomes = [
            future.result(timeout=10)
            for future in (pool.submit(realize_once), pool.submit(realize_once))
        ]

    assert sorted(outcomes) == [0, 1]
    with pg_factory() as db:
        rows = list(
            db.scalars(
                select(SavingsRealization).where(
                    SavingsRealization.tenant_id == seed.tenant_id,
                    SavingsRealization.opportunity_id == seed.opportunity_id,
                    SavingsRealization.business_event_id == seed.payment_event_id,
                )
            )
        )
        assert len(rows) == 1
        assert rows[0].realization_key == (f"payment:{seed.payment_event_id}:{seed.opportunity_id}")
        opportunity = db.get(SavingsOpportunity, seed.opportunity_id)
        assert opportunity is not None and opportunity.status == "realized"
        assert opportunity.version == 2
        assert (
            db.scalar(
                select(func.count())
                .select_from(BusinessEvent)
                .where(
                    BusinessEvent.tenant_id == seed.tenant_id,
                    BusinessEvent.aggregate_type == "savings_realization",
                    BusinessEvent.aggregate_id == rows[0].id,
                    BusinessEvent.event_type == "saving_action_completed",
                    BusinessEvent.causation_id == seed.payment_event_id,
                )
            )
            == 1
        )


def test_savings_event_is_append_only_at_database_boundary(
    pg_factory: sessionmaker[Session],
) -> None:
    seed = _seed_opportunity(pg_factory, owner_id="owner-append-only")
    request_id = f"append-only-{seed.suffix}"
    with pg_factory() as db:
        response = (
            SavingsActionService(db)
            .execute(
                seed.opportunity_id,
                SavingsOpportunityActionCreate(
                    action="accept",
                    request_id=request_id,
                    expected_version=1,
                    comment="创建不可变审计事件",
                ),
                _user("owner-append-only", seed.tenant_id, employee_id="owner-append-only"),
            )
            .response
        )
        event_id = response.event.id

    for statement in (
        "UPDATE savings_events SET action = 'tampered' WHERE id = :event_id",
        "DELETE FROM savings_events WHERE id = :event_id",
    ):
        with pg_factory() as db:
            with pytest.raises(DBAPIError, match="append-only"):
                db.execute(text(statement), {"event_id": event_id})
                db.commit()
            db.rollback()

    with pg_factory() as db:
        event = db.get(SavingsEvent, event_id)
        assert event is not None and event.action == "accept"
        assert event.response_json["event"]["id"] == event_id
