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.orm import Session

from app.api.deps import get_db
from app.api.v1.endpoints import linked_reimbursement_draft_jobs as draft_jobs_endpoint
from app.main import create_app
from app.models.employee import Employee
from app.models.financial_record import ExpenseClaim
from app.schemas.orchestrator import OrchestratorResponse, OrchestratorTraceSummary
from app.services.expense_cases import ExpenseCaseService
from app.services.linked_reimbursement_draft_jobs import (
    _build_direct_context_json,
    _find_application_claim,
    clear_linked_reimbursement_draft_jobs_for_tests,
)
from app.services.orchestrator import OrchestratorService
from app.test_helpers.db import build_in_memory_session_factory


def seed_employee_and_application(db: Session) -> None:
    employee = Employee(
        id="emp-linked-draft-fast",
        tenant_id="tenant-a",
        employee_no="E10001",
        name="张三",
        email="zhangsan@example.com",
        position="实施顾问",
        grade="P5",
    )
    application = ExpenseClaim(
        id="application-linked-draft-fast",
        tenant_id="tenant-a",
        claim_no="AP-202606-FAST",
        employee_id=employee.id,
        employee_name=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_all([employee, application])
    db.commit()


def build_client(monkeypatch) -> tuple[TestClient, object]:
    session_factory = build_in_memory_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
    monkeypatch.setattr(draft_jobs_endpoint, "get_session_factory", lambda: session_factory)
    return TestClient(app), session_factory


def test_linked_reimbursement_draft_job_runs_after_conversation_leaves(monkeypatch) -> None:
    clear_linked_reimbursement_draft_jobs_for_tests()
    captured_messages = []
    captured_users = []

    def fake_run(self, payload, *, current_user=None):
        captured_messages.append(payload.message)
        captured_users.append(current_user)
        return OrchestratorResponse(
            run_id="run-linked-draft-job",
            conversation_id=None,
            selected_agent="user_agent",
            route_reason="测试后台生成报销草稿。",
            permission_level="draft_write",
            status="succeeded",
            result={
                "message": "报销草稿已生成。",
                "draft_payload": {
                    "claim_id": "draft-linked-1",
                    "claim_no": "RE-202606-009",
                    "status": "draft",
                    "expense_type": "travel",
                },
            },
            requires_confirmation=False,
            trace_summary=OrchestratorTraceSummary(
                scenario="expense",
                intent="draft",
                tool_count=1,
                failed_tool_count=0,
                selected_capability_codes=[],
                degraded=False,
            ),
        )

    monkeypatch.setattr(OrchestratorService, "run", fake_run)
    try:
        client, _session_factory = build_client(monkeypatch)
        headers = {
            "x-auth-username": "zhangsan@example.com",
            "x-auth-name": "Zhang San",
            "x-auth-employee-no": "E10001",
            "x-auth-role-codes": "user",
        }

        response = client.post(
            "/api/v1/reimbursements/linked-reimbursement-draft-jobs",
            headers=headers,
            json={
                "message": "我要报销\n用户选择报销场景：差旅费\n关联申请单：AP-202606-001",
                "conversation_id": "inline-test",
                "context_json": {
                    "review_action": "save_draft",
                    "expense_scene_selection": {
                        "expense_type": "travel",
                        "expense_type_label": "差旅费",
                        "application_claim_no": "AP-202606-001",
                    },
                    "review_form_values": {
                        "application_claim_no": "AP-202606-001",
                    },
                },
            },
        )

        assert response.status_code == 202
        job_id = response.json()["job_id"]

        status_response = client.get(
            f"/api/v1/reimbursements/linked-reimbursement-draft-jobs/{job_id}",
            headers=headers,
        )
        assert status_response.status_code == 200
        payload = status_response.json()
        assert payload["status"] == "succeeded"
        assert payload["draft_payload"]["claim_no"] == "RE-202606-009"
        assert payload["run_id"] == "run-linked-draft-job"
        assert captured_messages == [
            "我要报销\n用户选择报销场景：差旅费\n关联申请单：AP-202606-001"
        ]
        assert len(captured_users) == 1
        assert captured_users[0] is not None
        assert captured_users[0].username == "zhangsan@example.com"
        assert captured_users[0].tenant_id == "default"
    finally:
        clear_linked_reimbursement_draft_jobs_for_tests()


def test_linked_job_overrides_forged_tenant_and_rejects_same_owner_cross_tenant(
    monkeypatch,
) -> None:
    clear_linked_reimbursement_draft_jobs_for_tests()
    captured_contexts = []
    captured_users = []

    def fake_run(self, payload, *, current_user=None):
        captured_contexts.append(dict(payload.context_json or {}))
        captured_users.append(current_user)
        return OrchestratorResponse(
            run_id="run-linked-tenant-guard",
            conversation_id=None,
            selected_agent="user_agent",
            route_reason="验证租户边界。",
            permission_level="draft_write",
            status="succeeded",
            result={
                "message": "报销草稿已生成。",
                "draft_payload": {
                    "claim_id": "tenant-guard-draft",
                    "claim_no": "RE-TENANT-GUARD",
                    "status": "draft",
                    "expense_type": "travel",
                },
            },
            requires_confirmation=False,
            trace_summary=OrchestratorTraceSummary(
                scenario="expense",
                intent="draft",
                tool_count=1,
                failed_tool_count=0,
                selected_capability_codes=[],
                degraded=False,
            ),
        )

    monkeypatch.setattr(OrchestratorService, "run", fake_run)
    try:
        client, _session_factory = build_client(monkeypatch)
        tenant_a_headers = {
            "x-auth-username": "same-owner@example.com",
            "x-auth-name": "Same Owner",
            "x-auth-role-codes": "user",
            "x-auth-tenant-id": "tenant-a",
        }
        response = client.post(
            "/api/v1/reimbursements/linked-reimbursement-draft-jobs",
            headers=tenant_a_headers,
            json={
                "message": "创建普通报销草稿",
                "context_json": {
                    "tenant_id": "tenant-forged",
                    "tenantId": "tenant-forged-camel",
                },
            },
        )

        assert response.status_code == 202
        assert captured_contexts == [
            {
                "tenant_id": "tenant-a",
                "entry_source": "workbench-ai",
                "session_type": "expense",
            }
        ]
        assert len(captured_users) == 1
        assert captured_users[0] is not None
        assert captured_users[0].tenant_id == "tenant-a"
        assert captured_users[0].tenant_id != "default"
        job_id = response.json()["job_id"]
        cross_tenant_response = client.get(
            f"/api/v1/reimbursements/linked-reimbursement-draft-jobs/{job_id}",
            headers={**tenant_a_headers, "x-auth-tenant-id": "tenant-b"},
        )
        assert cross_tenant_response.status_code == 404
    finally:
        clear_linked_reimbursement_draft_jobs_for_tests()


def test_linked_application_lookup_and_context_are_tenant_scoped() -> None:
    session_factory = build_in_memory_session_factory()
    with session_factory() as db:
        seed_employee_and_application(db)
        application = db.get(ExpenseClaim, "application-linked-draft-fast")
        assert application is not None
        ExpenseCaseService(db).ensure_case_for_claim(
            application,
            tenant_id="tenant-a",
        )
        db.commit()

        assert (
            _find_application_claim(
                db,
                claim_no="AP-202606-FAST",
                tenant_id="tenant-b",
            )
            is None
        )
        resolved = _find_application_claim(
            db,
            claim_no="AP-202606-FAST",
            tenant_id="tenant-a",
        )
        assert resolved is not None
        context = _build_direct_context_json(
            db,
            {
                "tenant_id": "tenant-forged",
                "tenantId": "tenant-forged-camel",
                "review_form_values": {
                    "application_claim_no": "AP-202606-FAST",
                },
            },
            tenant_id="tenant-a",
        )
        assert context["tenant_id"] == "tenant-a"
        assert "tenantId" not in context
        assert context["review_form_values"]["application_claim_id"] == application.id


def test_linked_reimbursement_draft_job_uses_direct_save_path(monkeypatch) -> None:
    clear_linked_reimbursement_draft_jobs_for_tests()

    def fail_if_orchestrator_runs(self, payload):
        raise AssertionError("linked draft job should not run full orchestrator")

    monkeypatch.setattr(OrchestratorService, "run", fail_if_orchestrator_runs)
    try:
        client, session_factory = build_client(monkeypatch)
        with session_factory() as db:
            seed_employee_and_application(db)

        headers = {
            "x-auth-username": "zhangsan@example.com",
            "x-auth-name": "Zhang San",
            "x-auth-employee-no": "E10001",
            "x-auth-role-codes": "user",
            "x-auth-tenant-id": "tenant-a",
        }

        response = client.post(
            "/api/v1/reimbursements/linked-reimbursement-draft-jobs",
            headers=headers,
            json={
                "message": "我要报销\n用户选择报销场景：差旅费\n关联申请单：AP-202606-FAST",
                "conversation_id": "inline-fast-test",
                "context_json": {
                    "name": "张三",
                    "review_action": "save_draft",
                    "expense_scene_selection": {
                        "expense_type": "travel",
                        "expense_type_label": "差旅费",
                        "application_claim_id": "application-linked-draft-fast",
                        "application_claim_no": "AP-202606-FAST",
                    },
                    "review_form_values": {
                        "expense_type": "差旅费",
                        "reason": "支撑国网仿生产服务器部署",
                        "location": "上海",
                        "time_range": "2026-02-20 至 2026-02-23",
                        "application_claim_id": "application-linked-draft-fast",
                        "application_claim_no": "AP-202606-FAST",
                        "application_reason": "支撑国网仿生产服务器部署",
                        "application_location": "上海",
                        "application_amount": "3000",
                        "application_amount_label": "¥3,000",
                        "application_business_time": "2026-02-20 至 2026-02-23",
                    },
                },
            },
        )

        assert response.status_code == 202
        job_id = response.json()["job_id"]

        status_response = client.get(
            f"/api/v1/reimbursements/linked-reimbursement-draft-jobs/{job_id}",
            headers=headers,
        )
        assert status_response.status_code == 200
        payload = status_response.json()
        assert payload["status"] == "succeeded"
        assert payload["draft_payload"]["claim_no"]
        assert payload["draft_payload"]["claim_id"]
        assert payload["run_id"].startswith("linked-reimbursement-draft-")

        with session_factory() as db:
            draft = db.get(ExpenseClaim, payload["draft_payload"]["claim_id"])
            assert draft is not None
            assert draft.status == "draft"
            assert draft.expense_type == "travel"
            assert draft.reason == "支撑国网仿生产服务器部署"
            assert draft.items == []
    finally:
        clear_linked_reimbursement_draft_jobs_for_tests()


def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_no_only(
    monkeypatch,
) -> None:
    clear_linked_reimbursement_draft_jobs_for_tests()

    def fail_if_orchestrator_runs(self, payload):
        raise AssertionError(
            "linked draft job should resolve application no without full orchestrator"
        )

    monkeypatch.setattr(OrchestratorService, "run", fail_if_orchestrator_runs)
    try:
        client, session_factory = build_client(monkeypatch)
        with session_factory() as db:
            seed_employee_and_application(db)

        headers = {
            "x-auth-username": "zhangsan@example.com",
            "x-auth-name": "Zhang San",
            "x-auth-employee-no": "E10001",
            "x-auth-role-codes": "user",
            "x-auth-tenant-id": "tenant-a",
        }

        response = client.post(
            "/api/v1/reimbursements/linked-reimbursement-draft-jobs",
            headers=headers,
            json={
                "message": "我要报销\n用户选择报销场景：差旅费\n关联申请单：AP-202606-FAST",
                "conversation_id": "inline-fast-no-id-test",
                "context_json": {
                    "name": "张三",
                    "review_action": "save_draft",
                    "expense_scene_selection": {
                        "expense_type": "travel",
                        "expense_type_label": "差旅费",
                        "application_claim_no": "AP-202606-FAST",
                    },
                    "review_form_values": {
                        "expense_type": "差旅费",
                        "reason": "支撑国网仿生产服务器部署",
                        "location": "上海",
                        "time_range": "2026-02-20 至 2026-02-23",
                        "application_claim_no": "AP-202606-FAST",
                        "application_reason": "支撑国网仿生产服务器部署",
                        "application_location": "上海",
                        "application_amount": "3000",
                    },
                },
            },
        )

        assert response.status_code == 202
        job_id = response.json()["job_id"]

        status_response = client.get(
            f"/api/v1/reimbursements/linked-reimbursement-draft-jobs/{job_id}",
            headers=headers,
        )
        assert status_response.status_code == 200
        payload = status_response.json()
        assert payload["status"] == "succeeded"
        assert payload["draft_payload"]["claim_no"]
        assert payload["draft_payload"]["claim_id"]

        with session_factory() as db:
            draft = db.get(ExpenseClaim, payload["draft_payload"]["claim_id"])
            assert draft is not None
            link_flag = next(
                flag for flag in draft.risk_flags_json if flag.get("source") == "application_link"
            )
            assert link_flag["application_claim_no"] == "AP-202606-FAST"
            assert link_flag["application_claim_id"] == "application-linked-draft-fast"
    finally:
        clear_linked_reimbursement_draft_jobs_for_tests()
