"""Phase 10.1 — Driver Management tests."""
from __future__ import annotations

import pytest

from app.events.publisher import get_event_publisher
from app.models.types import DriverLifecycleAction, DriverStatus
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
from app.validators.drivers import ensure_driver_lifecycle_transition
from shared.exceptions import AppError


async def _org(client, code="ORG_D"):
    resp = await client.post(
        "/api/v1/organizations",
        headers=tenant_headers(TENANT_A),
        json={"code": code, "name": code, "status": "active"},
    )
    assert resp.status_code == 201, resp.text
    return resp.json()


async def _hub(client, org_id, code="HUB_D"):
    resp = await client.post(
        "/api/v1/hubs",
        headers=tenant_headers(TENANT_A),
        json={"organization_id": org_id, "code": code, "name": code},
    )
    assert resp.status_code == 201, resp.text
    return resp.json()


async def _driver(client, org_id, *, code="DRV1", hub_id=None, status="pending"):
    payload = {
        "organization_id": org_id,
        "code": code,
        "display_name": f"Driver {code}",
        "mobile": "+989121234567",
        "status": status,
    }
    if hub_id:
        payload["hub_id"] = hub_id
    resp = await client.post(
        "/api/v1/drivers",
        headers=tenant_headers(TENANT_A),
        json=payload,
    )
    assert resp.status_code == 201, resp.text
    return resp.json()


def test_lifecycle_transition_matrix_unit():
    ensure_driver_lifecycle_transition(
        action=DriverLifecycleAction.ACTIVATE, current=DriverStatus.PENDING
    )
    with pytest.raises(AppError) as exc:
        ensure_driver_lifecycle_transition(
            action=DriverLifecycleAction.SUSPEND, current=DriverStatus.PENDING
        )
    assert exc.value.error_code == "invalid_driver_lifecycle_transition"
    with pytest.raises(AppError) as exc2:
        ensure_driver_lifecycle_transition(
            action=DriverLifecycleAction.ACTIVATE, current=DriverStatus.ARCHIVED
        )
    assert exc2.value.error_code == "driver_terminal"


@pytest.mark.asyncio
async def test_driver_lifecycle_flow(client):
    org = await _org(client, "LIFEORG")
    hub = await _hub(client, org["id"], "LIFEHUB")
    driver = await _driver(client, org["id"], code="LIFE1", hub_id=hub["id"])
    did = driver["id"]
    version = driver["version"]

    activated = await client.post(
        f"/api/v1/drivers/{did}/activate",
        headers=tenant_headers(TENANT_A),
        json={"version": version},
    )
    assert activated.status_code == 200, activated.text
    assert activated.json()["status"] == "active"
    version = activated.json()["version"]

    suspended = await client.post(
        f"/api/v1/drivers/{did}/suspend",
        headers=tenant_headers(TENANT_A),
        json={"version": version, "reason": "policy review"},
    )
    assert suspended.status_code == 200
    assert suspended.json()["status"] == "suspended"
    version = suspended.json()["version"]

    resumed = await client.post(
        f"/api/v1/drivers/{did}/resume",
        headers=tenant_headers(TENANT_A),
        json={"version": version},
    )
    assert resumed.status_code == 200
    assert resumed.json()["status"] == "active"
    version = resumed.json()["version"]

    deactivated = await client.post(
        f"/api/v1/drivers/{did}/deactivate",
        headers=tenant_headers(TENANT_A),
        json={"version": version, "reason": "left company"},
    )
    assert deactivated.status_code == 200
    assert deactivated.json()["status"] == "inactive"
    version = deactivated.json()["version"]

    archived = await client.post(
        f"/api/v1/drivers/{did}/archive",
        headers=tenant_headers(TENANT_A),
        json={"version": version, "reason": "closed"},
    )
    assert archived.status_code == 200
    assert archived.json()["status"] == "archived"

    life = await client.get(
        f"/api/v1/drivers/{did}/lifecycle",
        headers=tenant_headers(TENANT_A),
    )
    assert life.status_code == 200
    actions = {row["action"] for row in life.json()}
    assert {"activate", "suspend", "resume", "deactivate", "archive"} <= actions

    events = {e.event_type for e in get_event_publisher().published}
    assert "delivery.driver.created" in events
    assert "delivery.driver.activated" in events
    assert "delivery.driver.suspended" in events
    assert "delivery.driver.archived" in events
    assert "delivery.driver.status_changed" in events


@pytest.mark.asyncio
async def test_block_unblock_and_invalid_transition(client):
    org = await _org(client, "BLKORG")
    driver = await _driver(client, org["id"], code="BLK1", status="active")
    did = driver["id"]
    version = driver["version"]

    bad = await client.post(
        f"/api/v1/drivers/{did}/suspend",
        headers=tenant_headers(TENANT_A),
        json={"version": version},
    )
    assert bad.status_code == 422
    assert bad.json()["error"]["code"] == "reason_required"

    blocked = await client.post(
        f"/api/v1/drivers/{did}/block",
        headers=tenant_headers(TENANT_A),
        json={"version": version, "reason": "fraud"},
    )
    assert blocked.status_code == 200
    assert blocked.json()["status"] == "blocked"
    version = blocked.json()["version"]

    unblocked = await client.post(
        f"/api/v1/drivers/{did}/unblock",
        headers=tenant_headers(TENANT_A),
        json={"version": version},
    )
    assert unblocked.status_code == 200
    assert unblocked.json()["status"] == "inactive"


@pytest.mark.asyncio
async def test_credentials_documents_list_filter_search_sort(client):
    org = await _org(client, "SRCORG")
    await _driver(client, org["id"], code="AAA", status="active")
    d2 = await _driver(client, org["id"], code="BBB", status="pending")
    await _driver(client, org["id"], code="CCC", status="active")

    listed = await client.get(
        "/api/v1/drivers",
        headers=tenant_headers(TENANT_A),
        params={"status": "active", "sort_by": "code", "sort_dir": "asc", "page_size": 50},
    )
    assert listed.status_code == 200, listed.text
    body = listed.json()
    assert body["total"] == 2
    assert [i["code"] for i in body["items"]] == ["AAA", "CCC"]

    searched = await client.get(
        "/api/v1/drivers",
        headers=tenant_headers(TENANT_A),
        params={"q": "BBB"},
    )
    assert searched.status_code == 200
    assert searched.json()["total"] == 1
    assert searched.json()["items"][0]["code"] == "BBB"

    cred = await client.post(
        f"/api/v1/drivers/{d2['id']}/credentials",
        headers=tenant_headers(TENANT_A),
        json={"kind": "license", "number": "LIC-1", "is_verified": True},
    )
    assert cred.status_code == 201, cred.text

    docs = await client.post(
        f"/api/v1/drivers/{d2['id']}/documents",
        headers=tenant_headers(TENANT_A),
        json={
            "kind": "license_scan",
            "title": "License",
            "storage_file_ref": "storage://files/lic-1",
        },
    )
    assert docs.status_code == 201, docs.text

    creds = await client.get(
        f"/api/v1/drivers/{d2['id']}/credentials",
        headers=tenant_headers(TENANT_A),
    )
    assert creds.status_code == 200
    assert len(creds.json()) == 1

    documents = await client.get(
        f"/api/v1/drivers/{d2['id']}/documents",
        headers=tenant_headers(TENANT_A),
    )
    assert documents.status_code == 200
    assert len(documents.json()) == 1


@pytest.mark.asyncio
async def test_optimistic_lock_and_soft_delete(client):
    org = await _org(client, "OPTORG")
    driver = await _driver(client, org["id"], code="OPT1")
    did = driver["id"]

    conflict = await client.patch(
        f"/api/v1/drivers/{did}",
        headers=tenant_headers(TENANT_A),
        json={"display_name": "Nope", "version": 999},
    )
    assert conflict.status_code in (409, 422)

    updated = await client.patch(
        f"/api/v1/drivers/{did}",
        headers=tenant_headers(TENANT_A),
        json={"display_name": "Updated Name", "version": driver["version"]},
    )
    assert updated.status_code == 200
    assert updated.json()["display_name"] == "Updated Name"
    assert updated.json()["version"] == driver["version"] + 1

    deleted = await client.post(
        f"/api/v1/drivers/{did}/delete",
        headers=tenant_headers(TENANT_A),
    )
    assert deleted.status_code == 200
    assert deleted.json()["is_deleted"] is True

    missing = await client.get(
        f"/api/v1/drivers/{did}",
        headers=tenant_headers(TENANT_A),
    )
    assert missing.status_code == 404


@pytest.mark.asyncio
async def test_driver_tenant_isolation(client):
    org = await _org(client, "ISOORG")
    driver = await _driver(client, org["id"], code="ISO1")
    other = await client.get(
        f"/api/v1/drivers/{driver['id']}",
        headers=tenant_headers(TENANT_B),
    )
    assert other.status_code == 404

    listed = await client.get(
        "/api/v1/drivers",
        headers=tenant_headers(TENANT_B),
    )
    assert listed.status_code == 200
    assert listed.json()["total"] == 0


@pytest.mark.asyncio
async def test_permission_catalog(client):
    resp = await client.get(
        "/api/v1/permissions/catalog",
        headers=tenant_headers(TENANT_A),
    )
    assert resp.status_code == 200
    body = resp.json()
    assert "delivery.drivers.activate" in body["permissions"]
    assert body["count"] >= 40
