from __future__ import annotations

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

from auth_helpers import install_legacy_header_auth_override
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, select
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.agent_conversation import AgentConversation
from app.models.ai_application_preview import AIApplicationPreviewDecision
from app.models.ai_learning import AIDecisionFeedback
from app.models.employee import Employee
from app.models.expense_case import BusinessEvent, ExpenseCaseLink
from app.models.financial_record import ExpenseClaim
from app.services import attachment_association_jobs as attachment_jobs_module


def build_session_factory() -> sessionmaker[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 build_client() -> tuple[TestClient, sessionmaker[Session]]:
    session_factory = build_session_factory()
    app = create_app()
    install_legacy_header_auth_override(app)

    def override_db() -> Generator[Session, None, None]:
        db = session_factory()
        try:
            yield db
        finally:
            db.close()

    app.dependency_overrides[get_db] = override_db
    return TestClient(app), session_factory


def seed_employee(db: Session) -> None:
    manager = Employee(
        id="steward-action-manager",
        tenant_id="tenant-steward-action",
        employee_no="E90000",
        name="李总",
        email="leader@example.com",
        position="部门负责人",
        grade="P7",
    )
    employee = Employee(
        id="steward-action-employee",
        tenant_id="tenant-steward-action",
        employee_no="E90001",
        name="张三",
        email="zhangsan@example.com",
        position="实施工程师",
        grade="P4",
        manager=manager,
    )
    db.add_all([manager, employee])
    db.commit()


def auth_headers() -> dict[str, str]:
    return {
        "x-auth-username": "zhangsan@example.com",
        "x-auth-name": "Zhang San",
        "x-auth-employee-no": "E90001",
        "x-auth-employee-id": "steward-action-employee",
        "x-auth-session-id": "session-steward-action",
        "x-auth-tenant-id": "tenant-steward-action",
        "x-auth-role-codes": "user",
        "x-auth-department": "Delivery",
        "x-auth-position": "Engineer",
        "x-auth-grade": "P4",
        "x-auth-manager-name": "Leader",
    }


def base_application_task(requested_action: str = "save_draft") -> dict[str, object]:
    return {
        "task_id": "task_app_001",
        "task_type": "expense_application",
        "assigned_agent": "application_assistant",
        "title": "上海出差申请",
        "summary": "2026-02-20 至 2026-02-23 去上海出差，辅助国网仿生产服务器部署，火车出行。",
        "status": "needs_confirmation",
        "confidence": 0.96,
        "requested_action": requested_action,
        "ontology_fields": {
            "expense_type": "travel",
            "time_range": "2026-02-20 至 2026-02-23",
            "location": "上海",
            "reason": "辅助国网仿生产服务器部署",
            "transport_mode": "train",
        },
        "missing_fields": [],
        "confirmation_required": requested_action == "submit",
        "action_steps": [],
    }


def base_reimbursement_task() -> dict[str, object]:
    return {
        "task_id": "task_reim_001",
        "task_type": "reimbursement",
        "assigned_agent": "reimbursement_assistant",
        "title": "客户现场交通费报销",
        "summary": "2026-03-04 打车去客户现场，交通费 32 元。",
        "status": "needs_confirmation",
        "confidence": 0.9,
        "requested_action": "save_draft",
        "ontology_fields": {
            "expense_type": "transport",
            "time_range": "2026-03-04",
            "location": "客户现场",
            "reason": "客户现场沟通",
            "amount": "32元",
            "transport_mode": "taxi",
        },
        "missing_fields": [],
        "confirmation_required": False,
        "action_steps": [],
    }


def claim_count(db: Session) -> int:
    return len(db.scalars(select(ExpenseClaim)).all())


def issue_application_preview(
    client: TestClient,
    *,
    conversation_id: str,
    client_trace_id: str,
    task: dict[str, object] | None = None,
) -> dict[str, object]:
    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "build_application_preview",
            "message": (
                "2026-02-20 至 2026-02-23，去上海出差，"
                "辅助国网仿生产服务器部署，交通火车，申请金额3000元"
            ),
            "conversation_id": conversation_id,
            "client_trace_id": client_trace_id,
            "task": task or base_application_task(),
        },
    )
    assert response.status_code == 200, response.text
    payload = response.json()
    assert payload["status"] == "succeeded", payload
    return payload["result_payload"]


def seed_approved_application(db: Session) -> None:
    application = ExpenseClaim(
        id="application-action-approved",
        tenant_id="tenant-steward-action",
        claim_no="AAPPROVED1",
        employee_id="steward-action-employee",
        employee_name="张三",
        department_id="dept-delivery",
        department_name="交付部",
        project_code=None,
        expense_type="travel_application",
        reason="辅助国网仿生产服务器部署",
        location="上海",
        amount=3000,
        currency="CNY",
        invoice_count=0,
        occurred_at=datetime(2026, 2, 20, tzinfo=UTC),
        submitted_at=None,
        status="approved",
        approval_stage="已完成",
        risk_flags_json=[],
    )
    db.add(application)
    db.commit()


def test_steward_action_executor_rejects_unknown_action_without_creating_claim() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)
        before_count = claim_count(db)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "delete_all_claims",
            "message": "请执行未知动作",
            "task": base_application_task(),
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "blocked"
    assert payload["action_type"] == "delete_all_claims"
    assert "不支持" in payload["message"]
    with session_factory() as db:
        assert claim_count(db) == before_count


def test_steward_action_executor_blocks_attachment_action_without_receipts() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)
        before_count = claim_count(db)

    task = base_reimbursement_task()
    task["ontology_fields"] = {
        **task["ontology_fields"],
        "attachments": "taxi.png",
    }
    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "associate_attachments",
            "message": "关联附件 taxi.png",
            "task": task,
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "blocked"
    assert "receipt_id" in payload["message"] or "票据" in payload["message"]
    with session_factory() as db:
        assert claim_count(db) == before_count


def test_steward_action_executor_records_pending_interrupt_in_conversation_state() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "submit_application",
            "message": (
                "2026-02-20 至 2026-02-23，去上海出差，"
                "辅助国网仿生产服务器部署，交通火车，直接提交"
            ),
            "conversation_id": "conv-action-submit",
            "client_trace_id": "trace-submit-pending",
            "task": base_application_task("submit"),
            "confirmed": False,
            "context_json": {
                "precheck_result": {
                    "status": "ok",
                    "blocking": False,
                }
            },
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "needs_confirmation"
    with session_factory() as db:
        conversation = db.scalar(
            select(AgentConversation).where(
                AgentConversation.conversation_id == "conv-action-submit"
            )
        )
        assert conversation is not None
        checkpoint = conversation.state_json["steward_action_checkpoint"]
        assert checkpoint["pending_interrupt"]["client_trace_id"] == "trace-submit-pending"
        assert checkpoint["pending_interrupt"]["action_type"] == "submit_application"
        assert checkpoint["actions"]["trace-submit-pending"]["status"] == "needs_confirmation"


def test_steward_action_executor_builds_canonical_application_preview_decision() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    first = issue_application_preview(
        client,
        conversation_id="conv-action-preview",
        client_trace_id="trace-build-preview",
    )
    second = issue_application_preview(
        client,
        conversation_id="conv-action-preview",
        client_trace_id="trace-build-preview",
    )

    assert first["decision_id"]
    assert first["decision_id"] == second["decision_id"]
    assert first["application_preview"]["decisionId"] == first["decision_id"]
    assert first["application_preview"]["fields"]["location"] == "上海市"
    assert second["idempotent_replay"] is True


def test_steward_action_checkpoint_does_not_replay_across_tenants() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    shared_conversation_id = "conv-action-cross-tenant"
    shared_trace_id = "trace-build-cross-tenant"
    tenant_a_headers = {
        **auth_headers(),
        "x-auth-tenant-id": "tenant-steward-a",
    }
    tenant_b_headers = {
        **auth_headers(),
        "x-auth-tenant-id": "tenant-steward-b",
    }
    request_payload = {
        "action_type": "build_application_preview",
        "message": (
            "2026-02-20 至 2026-02-23，去上海出差，"
            "辅助国网仿生产服务器部署，交通火车，申请金额3000元"
        ),
        "conversation_id": shared_conversation_id,
        "client_trace_id": shared_trace_id,
        "task": base_application_task(),
    }

    tenant_a_response = client.post(
        "/api/v1/steward/actions/execute",
        headers=tenant_a_headers,
        json=request_payload,
    )
    tenant_b_response = client.post(
        "/api/v1/steward/actions/execute",
        headers=tenant_b_headers,
        json=request_payload,
    )

    assert tenant_a_response.status_code == 200, tenant_a_response.text
    assert tenant_b_response.status_code == 200, tenant_b_response.text
    tenant_a_payload = tenant_a_response.json()
    tenant_b_payload = tenant_b_response.json()
    assert tenant_a_payload["status"] == "succeeded"
    assert tenant_b_payload["status"] == "succeeded"
    assert tenant_b_payload["result_payload"].get("idempotent_replay") is not True
    assert (
        tenant_a_payload["result_payload"]["decision_id"]
        != tenant_b_payload["result_payload"]["decision_id"]
    )

    with session_factory() as db:
        decisions = list(db.scalars(select(AIApplicationPreviewDecision)).all())
        assert {decision.tenant_id for decision in decisions} == {
            "tenant-steward-a",
            "tenant-steward-b",
        }
        conversations = list(db.scalars(select(AgentConversation)).all())
        assert len(conversations) == 2
        assert {conversation.state_json.get("tenant_id") for conversation in conversations} == {
            "tenant-steward-a",
            "tenant-steward-b",
        }


def test_steward_action_executor_reuses_checkpoint_for_duplicate_trace_without_duplicate_draft(
) -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    issued = issue_application_preview(
        client,
        conversation_id="conv-action-draft",
        client_trace_id="trace-build-draft-preview",
    )
    request_payload = {
        "action_type": "save_application_draft",
        "message": (
            "2026-02-20 至 2026-02-23，去上海出差，"
            "辅助国网仿生产服务器部署，交通火车，保存草稿"
        ),
        "conversation_id": "conv-action-draft",
        "client_trace_id": "trace-save-draft",
        "decision_id": issued["decision_id"],
        "task": base_application_task("save_draft"),
    }
    first_response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json=request_payload,
    )
    second_response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json=request_payload,
    )

    assert first_response.status_code == 200
    assert second_response.status_code == 200
    first_payload = first_response.json()
    second_payload = second_response.json()
    assert first_payload["status"] == "succeeded"
    assert second_payload["status"] == "succeeded"
    assert (
        first_payload["result_payload"]["draft_payload"]["claim_id"]
        == second_payload["result_payload"]["draft_payload"]["claim_id"]
    )
    assert second_payload["result_payload"]["idempotent_replay"] is True
    with session_factory() as db:
        assert claim_count(db) == 1
        events = list(db.scalars(select(BusinessEvent)).all())
        assert len(events) == 1
        assert events[0].event_type == "claim_draft_created"


def test_steward_action_executor_requires_confirmation_before_submit_side_effect() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "submit_application",
            "message": (
                "2026-02-20 至 2026-02-23，去上海出差，"
                "辅助国网仿生产服务器部署，交通火车，直接提交"
            ),
            "task": base_application_task("submit"),
            "confirmed": False,
            "context_json": {
                "precheck_result": {
                    "status": "ok",
                    "blocking": False,
                }
            },
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "needs_confirmation"
    assert payload["requires_confirmation"] is True
    with session_factory() as db:
        assert claim_count(db) == 0


def test_steward_action_executor_saves_application_draft_from_action_step() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    issued = issue_application_preview(
        client,
        conversation_id="conv-action-save",
        client_trace_id="trace-build-save-preview",
    )
    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "save_application_draft",
            "message": (
                "2026-02-20 至 2026-02-23，去上海出差，"
                "辅助国网仿生产服务器部署，交通火车，保存草稿"
            ),
            "conversation_id": "conv-action-save",
            "client_trace_id": "trace-save-application-action",
            "decision_id": issued["decision_id"],
            "task": base_application_task("save_draft"),
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "succeeded"
    draft_payload = payload["result_payload"]["draft_payload"]
    assert draft_payload["draft_type"] == "expense_application"
    assert draft_payload["status"] == "draft"
    assert draft_payload["claim_no"].startswith("A")
    assert payload["result_payload"]["decision_id"]
    assert payload["result_payload"]["decision_id"] != issued["decision_id"]
    with session_factory() as db:
        claim = db.scalars(select(ExpenseClaim)).one()
        assert claim.status == "draft"
        assert claim.reason == "辅助国网仿生产服务器部署"
        consumed_decision = db.get(AIApplicationPreviewDecision, issued["decision_id"])
        assert consumed_decision is not None
        assert consumed_decision.status == "consumed"
        feedback = db.scalars(select(AIDecisionFeedback)).one()
        assert feedback.verification_status == "server_verified"
        event = db.scalars(
            select(BusinessEvent).where(BusinessEvent.aggregate_id == claim.id)
        ).one()
        assert event.event_type == "claim_draft_created"
        assert event.actor_id == "zhangsan@example.com"
        assert event.correlation_id == "application-preview-action:conv-action-save"
        link = db.scalars(
            select(ExpenseCaseLink).where(ExpenseCaseLink.resource_id == claim.id)
        ).one()
        assert link.relation_type == "application"


def test_steward_action_executor_blocks_save_without_decision_and_stable_trace() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "save_application_draft",
            "message": "保存申请草稿",
            "task": base_application_task("save_draft"),
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "blocked"
    assert payload["blocked_reasons"] == ["missing_decision_id"]

    issued = issue_application_preview(
        client,
        conversation_id="conv-action-missing-trace",
        client_trace_id="trace-build-missing-trace",
    )
    missing_trace_response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "save_application_draft",
            "message": "保存申请草稿",
            "conversation_id": "conv-action-missing-trace",
            "decision_id": issued["decision_id"],
            "task": base_application_task("save_draft"),
        },
    )
    missing_trace_payload = missing_trace_response.json()
    assert missing_trace_payload["status"] == "blocked"
    assert missing_trace_payload["blocked_reasons"] == ["missing_client_trace_id"]
    with session_factory() as db:
        assert claim_count(db) == 0


def test_steward_action_executor_submits_verified_application_after_confirmation_and_precheck(
) -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    issued = issue_application_preview(
        client,
        conversation_id="conv-action-verified-submit",
        client_trace_id="trace-build-submit-preview",
        task=base_application_task("submit"),
    )
    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "submit_application",
            "message": (
                "2026-02-20 至 2026-02-23，去上海出差，"
                "辅助国网仿生产服务器部署，交通火车，直接提交"
            ),
            "conversation_id": "conv-action-verified-submit",
            "client_trace_id": "trace-submit-verified-application",
            "decision_id": issued["decision_id"],
            "task": base_application_task("submit"),
            "confirmed": True,
            "context_json": {
                "precheck_result": {
                    "status": "ok",
                    "blocking": False,
                }
            },
        },
    )

    assert response.status_code == 200, response.text
    payload = response.json()
    assert payload["status"] == "succeeded"
    assert payload["result_payload"]["draft_payload"]["status"] == "submitted"
    assert payload["result_payload"]["decision_id"] is None
    with session_factory() as db:
        claim = db.scalars(select(ExpenseClaim)).one()
        assert claim.status == "submitted"


def test_steward_action_executor_creates_reimbursement_draft_from_action_step() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "create_reimbursement_draft",
            "message": "2026-03-04，打车去客户现场，交通费32元，保存草稿",
            "task": base_reimbursement_task(),
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "succeeded"
    assert payload["result_payload"]["status"] == "draft"
    assert payload["result_payload"]["claim_id"]
    with session_factory() as db:
        claim = db.scalars(select(ExpenseClaim)).one()
        assert claim.status == "draft"
        assert claim.expense_type == "transport"
        assert claim.reason == "客户现场沟通"


def test_steward_action_executor_links_application_when_creating_reimbursement_draft() -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)
        seed_approved_application(db)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "link_existing_application",
            "message": "关联申请单 AAPPROVED1，并保存报销草稿",
            "task": base_reimbursement_task(),
            "context_json": {
                "application_claim_id": "application-action-approved",
                "application_claim_no": "AAPPROVED1",
                "application_reason": "辅助国网仿生产服务器部署",
                "application_location": "上海",
                "application_business_time": "2026-02-20 至 2026-02-23",
            },
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "succeeded"
    assert payload["result_payload"]["status"] == "draft"
    with session_factory() as db:
        claims = db.scalars(select(ExpenseClaim)).all()
        reimbursement = next(claim for claim in claims if claim.id != "application-action-approved")
        assert reimbursement.status == "draft"
        link_flags = [
            flag
            for flag in list(reimbursement.risk_flags_json or [])
            if isinstance(flag, dict) and flag.get("source") == "application_link"
        ]
        assert link_flags
        assert link_flags[0]["application_claim_no"] == "AAPPROVED1"


def test_steward_action_executor_associates_receipt_attachments(monkeypatch) -> None:
    client, session_factory = build_client()
    with session_factory() as db:
        seed_employee(db)

    calls: list[dict[str, object]] = []

    def fake_run(self, *, receipt_ids, current_user):
        calls.append({
            "receipt_ids": list(receipt_ids),
            "username": current_user.username,
        })
        return {
            "claim_id": "claim-associated",
            "claim_no": "BX-20260220-001",
            "uploaded_count": 2,
            "skipped_count": 0,
        }

    monkeypatch.setattr(attachment_jobs_module.AttachmentAssociationJobRunner, "run", fake_run)

    response = client.post(
        "/api/v1/steward/actions/execute",
        headers=auth_headers(),
        json={
            "action_type": "associate_attachments",
            "message": "把两张火车票关联到报销草稿",
            "task": base_reimbursement_task(),
            "context_json": {
                "receipt_ids": ["receipt-001", "receipt-002"],
            },
        },
    )

    assert response.status_code == 200
    payload = response.json()
    assert payload["status"] == "succeeded"
    assert payload["result_payload"]["claim_no"] == "BX-20260220-001"
    assert calls == [
        {
            "receipt_ids": ["receipt-001", "receipt-002"],
            "username": "zhangsan@example.com",
        }
    ]
