from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal
from types import SimpleNamespace

from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool

from app.api.deps import CurrentUserContext
from app.db.base import Base
from app.models.budget import BudgetAllocation
from app.models.employee import Employee
from app.models.expense_case import BusinessEvent, ExpenseCase, ExpenseCaseLink
from app.models.financial_record import ExpenseClaim
from app.models.organization import OrganizationUnit
from app.schemas.ontology import OntologyParseResult
from app.schemas.steward import StewardActionExecuteRequest, StewardTask
from app.schemas.user_agent import UserAgentRequest
from app.services.expense_cases import ExpenseCaseService
from app.services.expense_claim_review_preview import ExpenseClaimReviewPreviewMixin
from app.services.expense_claim_workflow_constants import (
    APPLICATION_LINK_STATUS_STAGE,
    DIRECT_MANAGER_APPROVAL_STAGE,
)
from app.services.expense_claims import ExpenseClaimService
from app.services.steward_action_executor import StewardActionExecutor
from app.services.travel_reimbursement_calculator import (
    TravelReimbursementCalculatorService,
)
from app.services.user_agent import UserAgentService


def build_session() -> Session:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(bind=engine)
    return sessionmaker(bind=engine, autoflush=False, autocommit=False)()


def test_ontology_draft_event_uses_context_tenant() -> None:
    with build_session() as db:
        employee = Employee(
            employee_no="TENANT-DRAFT-EMPLOYEE",
            name="租户草稿员工",
            email="tenant-draft@example.com",
        )
        db.add(employee)
        db.commit()

        result = ExpenseClaimService(db).upsert_draft_from_ontology(
            run_id="tenant-draft-run",
            user_id=employee.email,
            message="2026-07-16 在上海拜访客户，交通费 32 元，保存草稿",
            ontology=OntologyParseResult(run_id="tenant-draft-run"),
            context_json={
                "tenant_id": "tenant-expense-a",
                "name": employee.name,
                "review_action": "save_draft",
                "review_form_values": {
                    "expense_type": "交通费",
                    "occurred_date": "2026-07-16",
                    "location": "上海",
                    "reason": "客户拜访",
                    "amount": "32元",
                },
            },
        )

        event = db.scalar(
            select(BusinessEvent).where(BusinessEvent.aggregate_id == result["claim_id"])
        )
        link = db.scalar(
            select(ExpenseCaseLink).where(ExpenseCaseLink.resource_id == result["claim_id"])
        )
        assert event is not None
        assert event.tenant_id == "tenant-expense-a"
        assert link is not None
        assert link.tenant_id == "tenant-expense-a"


class ReviewSubmitTenantProbe(ExpenseClaimReviewPreviewMixin):
    def __init__(self) -> None:
        self.submission_user: CurrentUserContext | None = None

    def upsert_draft_from_ontology(self, **_kwargs):
        return {"claim_id": "tenant-review-claim", "draft_only": True}

    def submit_claim(self, _claim_id: str, current_user: CurrentUserContext):
        self.submission_user = current_user
        return SimpleNamespace(
            id="tenant-review-claim",
            claim_no="BX-TENANT-REVIEW",
            status="submitted",
            approval_stage="直属领导审批",
            amount=Decimal("32.00"),
            invoice_count=1,
            risk_flags_json=[],
        )


def test_review_next_step_uses_context_tenant() -> None:
    probe = ReviewSubmitTenantProbe()
    result = probe.save_or_submit_from_ontology(
        run_id="tenant-review-run",
        user_id="tenant-review@example.com",
        message="确认提交",
        ontology=OntologyParseResult(run_id="tenant-review-run"),
        context_json={
            "review_action": "next_step",
            "tenant_id": "tenant-expense-b",
            "name": "租户预览员工",
        },
    )

    assert result["status"] == "submitted"
    assert probe.submission_user is not None
    assert probe.submission_user.tenant_id == "tenant-expense-b"


def test_review_calculators_use_context_tenant(monkeypatch) -> None:
    captured_tenants: list[str] = []

    def fake_calculate(_self, _payload, current_user):
        captured_tenants.append(current_user.tenant_id)
        return SimpleNamespace(
            grade="P4",
            days=2,
            matched_city="上海",
            hotel_rate=Decimal("300.00"),
            hotel_amount=Decimal("600.00"),
            total_allowance_rate=Decimal("100.00"),
            allowance_amount=Decimal("200.00"),
        )

    monkeypatch.setattr(TravelReimbursementCalculatorService, "calculate", fake_calculate)
    with build_session() as db:
        expense_service = ExpenseClaimService(db)
        expense_service._build_expense_review_preview_calculation_copy(
            user_id="tenant-calculator@example.com",
            message="2026-07-16 至 2026-07-17 去上海出差，交通费 32 元",
            ontology=OntologyParseResult(run_id="tenant-calculator-preview"),
            context_json={
                "tenant_id": "tenant-calculator",
                "employee_grade": "P4",
                "location": "上海",
                "occurred_date": "2026-07-16",
                "amount": "32元",
            },
        )

        payload = UserAgentRequest(
            run_id="tenant-calculator-message",
            user_id="tenant-calculator@example.com",
            message="上海出差",
            ontology=OntologyParseResult(run_id="tenant-calculator-message"),
            context_json={
                "tenant_id": "tenant-calculator",
                "grade": "P4",
                "name": "租户测算员工",
            },
        )
        UserAgentService(db)._build_travel_receipt_estimate_copy(
            payload,
            travel_receipt_state={
                "destination": "上海",
                "days": 2,
                "ticket_type_label": "火车票",
                "ticket_amount": "32",
            },
        )

    assert captured_tenants == ["tenant-calculator", "tenant-calculator"]


def test_steward_reimbursement_context_uses_current_tenant() -> None:
    task = StewardTask(
        task_id="tenant-steward-task",
        task_type="reimbursement",
        assigned_agent="reimbursement_assistant",
        title="租户报销",
        summary="上海客户拜访交通费 32 元",
        requested_action="save_draft",
        ontology_fields={
            "expense_type": "transport",
            "time_range": "2026-07-16",
            "location": "上海",
            "reason": "客户拜访",
            "amount": "32元",
        },
        missing_fields=[],
        confirmation_required=False,
    )
    request = StewardActionExecuteRequest(
        action_type="create_reimbursement_draft",
        message="保存报销草稿",
        task=task,
    )
    current_user = CurrentUserContext(
        username="tenant-steward@example.com",
        name="租户小财管家用户",
        role_codes=["user"],
        is_admin=False,
        tenant_id="tenant-steward",
    )

    with build_session() as db:
        context = StewardActionExecutor(db)._build_reimbursement_context_json(
            request,
            current_user,
        )

    assert context["tenant_id"] == "tenant-steward"


def test_application_approval_keeps_case_at_approved_to_spend() -> None:
    with build_session() as db:
        department = OrganizationUnit(
            tenant_id="tenant-case-stage",
            unit_code="TENANT-CASE-TRAVEL",
            name="租户差旅部",
            unit_type="department",
        )
        manager = Employee(
            tenant_id="tenant-case-stage",
            employee_no="TENANT-CASE-MANAGER",
            name="租户差旅经理",
            email="tenant-case-manager@example.com",
            organization_unit=department,
        )
        employee = Employee(
            tenant_id="tenant-case-stage",
            employee_no="TENANT-CASE-EMPLOYEE",
            name="租户差旅员工",
            email="tenant-case-employee@example.com",
            manager=manager,
            organization_unit=department,
        )
        db.add_all([department, manager, employee])
        db.flush()
        db.add(
            BudgetAllocation(
                budget_no="BUD-TENANT-CASE-TRAVEL",
                fiscal_year=2026,
                period_type="year",
                period_key="2026",
                department_id=department.id,
                department_name=department.name,
                cost_center=None,
                project_code=None,
                subject_code="travel",
                subject_name="差旅费",
                original_amount=Decimal("50000.00"),
                adjusted_amount=Decimal("0.00"),
                status="active",
                warning_threshold=Decimal("80.00"),
                control_action="block",
            )
        )
        application = ExpenseClaim(
            tenant_id="tenant-case-stage",
            claim_no="AP-TENANT-CASE-GENERATE",
            employee_id=employee.id,
            employee_name=employee.name,
            department_id=department.id,
            department_name=department.name,
            project_code="PRJ-TENANT-CASE",
            expense_type="travel_application",
            reason="客户现场差旅",
            location="上海",
            amount=Decimal("500.00"),
            currency="CNY",
            invoice_count=0,
            occurred_at=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
            submitted_at=datetime(2026, 7, 16, 10, 0, tzinfo=UTC),
            status="submitted",
            approval_stage=DIRECT_MANAGER_APPROVAL_STAGE,
            risk_flags_json=[],
        )
        db.add(application)
        db.flush()
        ExpenseCaseService(db).ensure_case_for_claim(
            application,
            tenant_id="tenant-case-stage",
        )
        db.commit()

        approved = ExpenseClaimService(db).approve_claim(
            application.id,
            CurrentUserContext(
                username=manager.email,
                name=manager.name,
                role_codes=["manager"],
                is_admin=False,
                tenant_id="tenant-case-stage",
            ),
            opinion="业务必要，同意申请",
        )

        assert approved is not None
        assert approved.status == "approved"
        assert approved.approval_stage == APPLICATION_LINK_STATUS_STAGE
        expense_case = db.scalar(select(ExpenseCase))
        assert expense_case is not None
        assert expense_case.tenant_id == "tenant-case-stage"
        assert expense_case.current_stage == "approved_to_spend"
        events = list(
            db.scalars(
                select(BusinessEvent)
                .where(BusinessEvent.expense_case_id == expense_case.id)
                .order_by(BusinessEvent.occurred_at)
            ).all()
        )
        assert [event.event_type for event in events] == [
            "application_approved",
            "reimbursement_draft_generated",
        ]
        assert {event.tenant_id for event in events} == {"tenant-case-stage"}
