import json

from fastapi.testclient import TestClient
from sqlalchemy import select

import services.sales_service.app as sales_module
from services.shared.db import get_session
from services.shared.sql_models import EventOutboxRow
from services.shared.sales_sql_models import SalesStageHistoryRow


def _headers(tenant_id: str = "tenant_state", role: str = "admin") -> dict[str, str]:
    return {"X-User": "admin", "X-Role": role, "X-Tenant-ID": tenant_id}


def _deal_payload(seed: str = "state", **overrides: object) -> dict:
    payload = {
        "stage_id": "new_qualified_lead",
        "scenario_type": "quick_sale",
        "priority": 3,
        "title": f"State machine deal {seed}",
        "need_summary": "Need a managed sales flow.",
        "document_required": False,
        "preferred_channel": "telegram",
        "current_channel": "telegram",
    }
    payload.update(overrides)
    return payload


def _create_deal(client: TestClient, tenant_id: str = "tenant_state", **overrides: object) -> dict:
    response = client.post("/api/v1/deals", json=_deal_payload(tenant_id, **overrides), headers=_headers(tenant_id))
    assert response.status_code == 200
    return response.json()


def _change_stage(
    client: TestClient,
    deal_id: str,
    target_stage_code: str,
    tenant_id: str = "tenant_state",
    *,
    reason: str | None = None,
    metadata: dict | None = None,
    force: bool = False,
    role: str = "admin",
) -> dict:
    payload = {"target_stage_code": target_stage_code, "metadata": metadata or {}, "force": force}
    if reason is not None:
        payload["reason"] = reason
    response = client.post(f"/api/v1/deals/{deal_id}/change-stage", json=payload, headers=_headers(tenant_id, role))
    assert response.status_code == 200
    return response.json()


def _prepare_need_confirmed(client: TestClient, deal_id: str, tenant_id: str = "tenant_state") -> None:
    _change_stage(client, deal_id, "active_text_communication", tenant_id, reason="text started")
    _change_stage(client, deal_id, "need_confirmed", tenant_id, reason="need confirmed")


def _prepare_invoice(client: TestClient, deal_id: str, tenant_id: str = "tenant_state") -> dict:
    _prepare_need_confirmed(client, deal_id, tenant_id)
    created = client.post(
        f"/api/v1/deals/{deal_id}/invoices",
        json={"amount": 1000, "currency": "KZT", "due_date": "2026-05-15"},
        headers=_headers(tenant_id),
    )
    assert created.status_code == 200
    sent = client.post(f"/api/v1/invoices/{created.json()['invoice_id']}/send", headers=_headers(tenant_id))
    assert sent.status_code == 200
    return sent.json()


def _stage_id(client: TestClient, tenant_id: str, code: str) -> str:
    pipelines = client.get("/api/v1/pipelines", headers=_headers(tenant_id))
    assert pipelines.status_code == 200
    default_pipeline = next(item for item in pipelines.json() if item["is_default"])
    stages = client.get(f"/api/v1/pipelines/{default_pipeline['pipeline_id']}/stages", headers=_headers(tenant_id))
    assert stages.status_code == 200
    return next(item["stage_id"] for item in stages.json() if item["code"] == code)


def _event_payloads(event_type: str, tenant_id: str) -> list[dict]:
    session = get_session()
    try:
        rows = session.execute(
            select(EventOutboxRow)
            .where(EventOutboxRow.producer_service == "sales-service", EventOutboxRow.event_type == event_type)
            .order_by(EventOutboxRow.id.asc())
        ).scalars().all()
        payloads = []
        for row in rows:
            envelope = json.loads(row.payload_json or "{}")
            payload = envelope.get("payload") if isinstance(envelope.get("payload"), dict) else {}
            if payload.get("tenant_id") == tenant_id:
                payloads.append(payload)
        return payloads
    finally:
        session.close()


def test_valid_stage_transition_succeeds():
    client = TestClient(sales_module.app)
    deal = _create_deal(client)

    changed = _change_stage(client, deal["deal_id"], "warm_lead", reason="lead warmed")

    assert changed["stage"]["code"] == "warm_lead"


def test_invalid_stage_transition_fails():
    client = TestClient(sales_module.app)
    deal = _create_deal(client)

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "offer_sent", "reason": "skip pipeline"},
        headers=_headers(),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "invalid_stage_transition"


def test_change_stage_missing_deal_returns_state_machine_error():
    client = TestClient(sales_module.app)

    response = client.post(
        "/api/v1/deals/sde_missing/change-stage",
        json={"target_stage_code": "warm_lead", "reason": "missing deal"},
        headers=_headers(),
    )

    assert response.status_code == 404
    assert response.json()["error"] == "deal_not_found"


def test_cannot_jump_from_new_lead_to_paid():
    client = TestClient(sales_module.app)
    deal = _create_deal(client)

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "paid", "reason": "jump"},
        headers=_headers(),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "invalid_stage_transition"


def test_cannot_set_stage_from_another_tenant():
    client = TestClient(sales_module.app)
    foreign_stage_id = _stage_id(client, "tenant_state_foreign", "warm_lead")
    deal = _create_deal(client, "tenant_state_owner")

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_id": foreign_stage_id, "reason": "foreign stage"},
        headers=_headers("tenant_state_owner"),
    )

    assert response.status_code == 403
    assert response.json()["error"] == "cross_tenant_stage_forbidden"


def test_cannot_set_stage_from_another_pipeline():
    client = TestClient(sales_module.app)
    tenant_id = "tenant_state_pipeline"
    deal = _create_deal(client, tenant_id)
    pipeline = client.post(
        "/api/v1/pipelines",
        json={"code": "enterprise_sales", "name": "Enterprise Sales"},
        headers=_headers(tenant_id),
    )
    assert pipeline.status_code == 200
    stage = client.post(
        f"/api/v1/pipelines/{pipeline.json()['pipeline_id']}/stages",
        json={"code": "enterprise_warm", "name": "Enterprise Warm", "category": "entry", "sort_order": 10},
        headers=_headers(tenant_id),
    )
    assert stage.status_code == 200

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_id": stage.json()["stage_id"], "reason": "wrong pipeline"},
        headers=_headers(tenant_id),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "invalid_stage_pipeline"


def test_offer_sent_requires_offer():
    client = TestClient(sales_module.app)
    deal = _create_deal(client)
    _prepare_need_confirmed(client, deal["deal_id"])
    _change_stage(client, deal["deal_id"], "offer_preparing", reason="prepare offer")

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "offer_sent", "reason": "send missing offer"},
        headers=_headers(),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "offer_required_before_offer_sent"


def test_invoice_sent_requires_invoice():
    client = TestClient(sales_module.app)
    deal = _create_deal(client)
    _prepare_need_confirmed(client, deal["deal_id"])
    _change_stage(client, deal["deal_id"], "invoice_preparing", reason="prepare invoice")

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "invoice_sent", "reason": "send missing invoice"},
        headers=_headers(),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "invoice_required_before_invoice_sent"


def test_won_requires_payment_when_payment_required():
    client = TestClient(sales_module.app)
    deal = _create_deal(client, payment_required=True)
    escalated = client.post(
        f"/api/v1/deals/{deal['deal_id']}/escalate",
        json={"escalation_type": "manual_review", "reason": "Need human review", "severity": "medium"},
        headers=_headers(),
    )
    assert escalated.status_code == 200

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "won", "reason": "manual win"},
        headers=_headers(),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "payment_required_before_won"


def test_lost_requires_reason():
    client = TestClient(sales_module.app)
    deal = _create_deal(client)

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "lost"},
        headers=_headers(),
    )

    assert response.status_code == 400
    assert response.json()["error"] == "lost_reason_required"


def test_successful_transition_writes_stage_history():
    client = TestClient(sales_module.app)
    deal = _create_deal(client, "tenant_state_history")

    _change_stage(client, deal["deal_id"], "warm_lead", "tenant_state_history", reason="lead warmed")

    session = get_session()
    try:
        rows = session.execute(
            select(SalesStageHistoryRow).where(
                SalesStageHistoryRow.tenant_id == "tenant_state_history",
                SalesStageHistoryRow.deal_id == deal["deal_id"],
            )
        ).scalars().all()
        assert rows
        assert rows[-1].reason == "lead warmed"
    finally:
        session.close()


def test_successful_transition_publishes_deal_stage_changed():
    client = TestClient(sales_module.app)
    deal = _create_deal(client, "tenant_state_event")

    _change_stage(client, deal["deal_id"], "warm_lead", "tenant_state_event", reason="lead warmed")

    payload = _event_payloads("deal.stage_changed", "tenant_state_event")[-1]
    assert payload["deal_id"] == deal["deal_id"]
    assert payload["from_stage_code"] == "new_qualified_lead"
    assert payload["to_stage_code"] == "warm_lead"


def test_won_transition_sets_status_and_closed_at():
    client = TestClient(sales_module.app)
    tenant_id = "tenant_state_won"
    deal = _create_deal(client, tenant_id)
    invoice = _prepare_invoice(client, deal["deal_id"], tenant_id)

    response = client.post(
        "/api/v1/payments/webhook",
        json={
            "deal_id": deal["deal_id"],
            "invoice_id": invoice["invoice_id"],
            "payment_provider": "manual",
            "external_payment_id": "state-paid-1",
            "amount": 1000,
            "currency": "KZT",
            "status": "success",
        },
        headers=_headers(tenant_id),
    )

    assert response.status_code == 200
    refreshed = client.get(f"/api/v1/deals/{deal['deal_id']}", headers=_headers(tenant_id))
    assert refreshed.status_code == 200
    assert refreshed.json()["status"] == "won"
    assert refreshed.json()["closed_at"]
    assert refreshed.json()["stage"]["code"] == "won"


def test_lost_transition_sets_status_and_closed_at():
    client = TestClient(sales_module.app)
    deal = _create_deal(client, "tenant_state_lost")

    changed = _change_stage(client, deal["deal_id"], "lost", "tenant_state_lost", reason="not interested")

    assert changed["status"] == "lost"
    assert changed["closed_at"]
    assert changed["lost_reason"] == "not interested"


def test_payment_webhook_transitions_deal_to_paid_and_won():
    client = TestClient(sales_module.app)
    tenant_id = "tenant_state_payment"
    deal = _create_deal(client, tenant_id)
    invoice = _prepare_invoice(client, deal["deal_id"], tenant_id)

    response = client.post(
        "/api/v1/payments/webhook",
        json={
            "deal_id": deal["deal_id"],
            "invoice_id": invoice["invoice_id"],
            "payment_provider": "manual",
            "external_payment_id": "state-paid-2",
            "amount": 1000,
            "currency": "KZT",
            "status": "success",
        },
        headers=_headers(tenant_id),
    )

    assert response.status_code == 200
    workspace = client.get(f"/api/v1/deals/{deal['deal_id']}/workspace", headers=_headers(tenant_id))
    assert workspace.status_code == 200
    stage_codes = [item["to_stage"]["code"] for item in workspace.json()["stage_history"]]
    assert "paid" in stage_codes
    assert "won" in stage_codes


def test_force_transition_requires_system_or_admin_actor():
    client = TestClient(sales_module.app)
    deal = _create_deal(client, "tenant_state_force")

    response = client.post(
        f"/api/v1/deals/{deal['deal_id']}/change-stage",
        json={"target_stage_code": "warm_lead", "reason": "operator force", "force": True},
        headers=_headers("tenant_state_force", role="operator"),
    )

    assert response.status_code == 403
    assert response.json()["error"] == "force_transition_forbidden"
