from __future__ import annotations

from collections.abc import Generator
from datetime import UTC, datetime
from decimal import Decimal

import pytest
from auth_helpers import install_legacy_header_auth_override
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool

from app.api.deps import get_db
from app.db.base import Base
from app.main import create_app
from app.models.employee import Employee
from app.models.financial_record import ExpenseClaim
from app.services.expense_cases import ExpenseCaseService
from app.services.expense_claim_workflow_constants import (
    DIRECT_MANAGER_APPROVAL_STAGE,
    FINANCE_APPROVAL_STAGE,
)


@pytest.fixture
def http_context() -> Generator[tuple[TestClient, sessionmaker[Session]], None, None]:
    engine = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(bind=engine)
    session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
    app = create_app()
    install_legacy_header_auth_override(app)

    def override_db() -> Generator[Session, None, None]:
        with session_factory() as db:
            yield db

    app.dependency_overrides[get_db] = override_db
    client = TestClient(app)
    try:
        yield client, session_factory
    finally:
        client.close()
        app.dependency_overrides.clear()
        engine.dispose()


def seed_timeline(
    db: Session,
    *,
    claim_no: str = "RE-TIMELINE-001",
    expense_type: str = "travel",
    relation_type: str | None = None,
    approval_stage: str = DIRECT_MANAGER_APPROVAL_STAGE,
    with_case: bool = True,
    tenant_id: str = "default",
) -> ExpenseClaim:
    manager = Employee(
        id="manager-1",
        employee_no="M001",
        name="李经理",
        email="manager@example.com",
    )
    owner = Employee(
        id="owner-1",
        employee_no="E001",
        name="张三",
        email="owner@example.com",
        manager=manager,
    )
    claim = ExpenseClaim(
        id="claim-1",
        claim_no=claim_no,
        employee_id=owner.id,
        employee_name=owner.name,
        department_name="市场部",
        expense_type=expense_type,
        reason="客户现场差旅",
        location="上海",
        amount=Decimal("880.00"),
        currency="CNY",
        invoice_count=1,
        occurred_at=datetime(2026, 7, 13, 9, 0, tzinfo=UTC),
        submitted_at=datetime(2026, 7, 13, 10, 0, tzinfo=UTC),
        status="submitted",
        approval_stage=approval_stage,
        risk_flags_json=[],
    )
    db.add_all([manager, owner, claim])
    db.flush()

    if with_case:
        ExpenseCaseService(db).record_claim_event(
            claim,
            event_type="claim_submitted",
            actor_id=owner.email,
            tenant_id=tenant_id,
            correlation_id="timeline-http-permission",
            idempotency_key="timeline-http-permission",
            previous_status="draft",
            previous_approval_stage="待提交",
            relation_type=relation_type,
            extra_payload={
                "opinion": "同意，按计划执行。",
                "internal_route": "finance-secret-route",
                "archived_applications": [
                    {
                        "application_claim_id": "internal-application-id",
                        "application_claim_no": "AP-TIMELINE-001",
                        "archive_event_id": "internal-archive-event-id",
                    }
                ],
            },
        )
    db.commit()
    return claim


@pytest.mark.parametrize(
    ("approval_stage", "headers"),
    [
        (
            DIRECT_MANAGER_APPROVAL_STAGE,
            {
                "x-auth-username": "owner@example.com",
                "x-auth-name": "Owner",
                "x-auth-employee-no": "E001",
                "x-auth-role-codes": "user",
            },
        ),
        (
            DIRECT_MANAGER_APPROVAL_STAGE,
            {
                "x-auth-username": "manager@example.com",
                "x-auth-name": "Manager",
                "x-auth-employee-no": "M001",
                "x-auth-role-codes": "approver",
            },
        ),
        (
            FINANCE_APPROVAL_STAGE,
            {
                "x-auth-username": "finance@example.com",
                "x-auth-name": "Finance",
                "x-auth-role-codes": "finance",
            },
        ),
        (
            DIRECT_MANAGER_APPROVAL_STAGE,
            {
                "x-auth-username": "admin",
                "x-auth-name": "Admin",
                "x-auth-is-admin": "true",
            },
        ),
    ],
    ids=["owner", "current-manager", "finance", "admin"],
)
def test_expense_case_timeline_allows_supported_viewers(
    http_context: tuple[TestClient, sessionmaker[Session]],
    approval_stage: str,
    headers: dict[str, str],
) -> None:
    client, session_factory = http_context
    with session_factory() as db:
        claim = seed_timeline(db, approval_stage=approval_stage)
        claim_id = claim.id

    response = client.get(f"/api/v1/expense-cases/by-claim/{claim_id}", headers=headers)

    assert response.status_code == 200
    payload = response.json()
    assert set(payload) == {"id", "case_no", "current_stage", "status", "links", "events"}
    assert payload["case_no"] == "CASE-RE-TIMELINE-001"
    assert payload["links"] == [{"relation_type": "claim"}]
    assert len(payload["events"]) == 1
    event = payload["events"][0]
    assert set(event) == {
        "id",
        "event_type",
        "actor_id",
        "actor_type",
        "payload_json",
        "occurred_at",
    }
    assert event["actor_id"] == "owner@example.com"
    assert event["payload_json"]["opinion"] == "同意，按计划执行。"
    assert event["payload_json"]["archived_applications"] == [
        {"application_claim_no": "AP-TIMELINE-001"}
    ]
    serialized = response.text
    for internal_value in (
        "idempotency_key",
        "correlation_id",
        "causation_id",
        "delivery_status",
        "resource_id",
        "internal_route",
        "internal-application-id",
        "internal-archive-event-id",
    ):
        assert internal_value not in serialized


def test_expense_case_timeline_hides_claim_existence_from_unrelated_user(
    http_context: tuple[TestClient, sessionmaker[Session]],
) -> None:
    client, session_factory = http_context
    with session_factory() as db:
        claim_id = seed_timeline(db).id

    response = client.get(
        f"/api/v1/expense-cases/by-claim/{claim_id}",
        headers={
            "x-auth-username": "unrelated@example.com",
            "x-auth-name": "Unrelated User",
            "x-auth-employee-no": "E999",
            "x-auth-role-codes": "user",
        },
    )

    assert response.status_code == 404
    assert response.json()["detail"] == "费用单据不存在。"


def test_expense_case_timeline_rejects_cross_tenant_lookup(
    http_context: tuple[TestClient, sessionmaker[Session]],
) -> None:
    client, session_factory = http_context
    with session_factory() as db:
        claim_id = seed_timeline(db, tenant_id="tenant-a").id

    response = client.get(
        f"/api/v1/expense-cases/by-claim/{claim_id}",
        headers={
            "x-auth-username": "owner@example.com",
            "x-auth-name": "Owner",
            "x-auth-employee-no": "E001",
            "x-auth-role-codes": "user",
            "x-auth-tenant-id": "tenant-b",
        },
    )

    assert response.status_code == 404
    assert response.json()["detail"] == "费用单据不存在。"


def test_expense_case_timeline_returns_not_covered_for_claim_without_case(
    http_context: tuple[TestClient, sessionmaker[Session]],
) -> None:
    client, session_factory = http_context
    with session_factory() as db:
        claim_id = seed_timeline(db, with_case=False).id

    response = client.get(
        f"/api/v1/expense-cases/by-claim/{claim_id}",
        headers={
            "x-auth-username": "owner@example.com",
            "x-auth-name": "Owner",
            "x-auth-employee-no": "E001",
            "x-auth-role-codes": "user",
        },
    )

    assert response.status_code == 404
    assert response.json()["detail"] == "该单据尚未纳入统一费用事件。"


def test_expense_case_timeline_returns_safe_summary_for_all_linked_claims(
    http_context: tuple[TestClient, sessionmaker[Session]],
) -> None:
    client, session_factory = http_context
    with session_factory() as db:
        application = seed_timeline(
            db,
            claim_no="AP-TIMELINE-001",
            expense_type="travel_application",
            relation_type="application",
        )
        service = ExpenseCaseService(db)
        expense_case = service.get_timeline_for_claim(application.id)
        assert expense_case is not None
        reimbursement = ExpenseClaim(
            id="claim-2",
            claim_no="RE-TIMELINE-002",
            employee_id=application.employee_id,
            employee_name=application.employee_name,
            department_name=application.department_name,
            expense_type="travel",
            reason="客户现场差旅报销",
            location="上海",
            amount=Decimal("880.00"),
            currency="CNY",
            invoice_count=1,
            occurred_at=application.occurred_at,
            submitted_at=application.submitted_at,
            status="submitted",
            approval_stage=FINANCE_APPROVAL_STAGE,
            risk_flags_json=[],
        )
        db.add(reimbursement)
        db.flush()
        service.record_claim_event(
            reimbursement,
            event_type="claim_submitted",
            actor_id="owner@example.com",
            expense_case=expense_case,
            relation_type="generated_reimbursement",
            correlation_id="linked-reimbursement",
            idempotency_key="linked-reimbursement",
            previous_status="draft",
            previous_approval_stage="待提交",
        )
        db.commit()

    response = client.get(
        "/api/v1/expense-cases/by-claim/claim-1",
        headers={
            "x-auth-username": "owner@example.com",
            "x-auth-name": "Owner",
            "x-auth-employee-no": "E001",
            "x-auth-role-codes": "user",
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert {item["relation_type"] for item in payload["links"]} == {
        "application",
        "generated_reimbursement",
    }
    assert [event["event_type"] for event in payload["events"]] == [
        "claim_submitted",
        "claim_submitted",
    ]
