"""Enterprise validation suite — rate limit, idempotency, failover, metrics, OTP isolation."""
from __future__ import annotations

import asyncio
import uuid

import pytest
from httpx import AsyncClient

from app.events.publisher import get_event_publisher
from app.events.types import CommunicationEventType
from app.providers import reset_provider_registry_for_tests
from app.services.queue_engine import get_rate_limiter
from app.tests.conftest import tenant_headers


async def _mock_provider(client, headers, *, name, priority, force_fail=False, rate_limit=None):
    payload = {
        "name": name,
        "provider_kind": "mock",
        "channel": "sms",
        "priority": priority,
        "credentials": {"force_fail": True} if force_fail else {},
        "max_retries": 1,
    }
    if rate_limit is not None:
        payload["rate_limit_per_minute"] = rate_limit
    return await client.post("/api/v1/providers", headers=headers, json=payload)


@pytest.mark.asyncio
async def test_correlation_id_idempotency(client: AsyncClient):
    h = tenant_headers(uuid.uuid4())
    await _mock_provider(client, h, name="P", priority=1)
    body = {
        "channel": "sms",
        "to_address": "+989120000001",
        "body": "hello",
        "correlation_id": "idem-1",
        "process_immediately": True,
    }
    first = await client.post("/api/v1/messages/send", headers=h, json=body)
    second = await client.post("/api/v1/messages/send", headers=h, json=body)
    assert first.status_code == 201
    assert second.status_code == 201
    assert first.json()[0]["id"] == second.json()[0]["id"]
    listed = await client.get("/api/v1/messages", headers=h)
    assert len(listed.json()) == 1


@pytest.mark.asyncio
async def test_rate_limit_defers_without_duplicate_send(client: AsyncClient):
    get_rate_limiter().reset_stats()
    mock = reset_provider_registry_for_tests()
    h = tenant_headers(uuid.uuid4())
    await _mock_provider(client, h, name="Limited", priority=1, rate_limit=1)

    # First send consumes the only slot
    r1 = await client.post(
        "/api/v1/messages/send",
        headers=h,
        json={
            "channel": "sms",
            "to_address": "+989120000002",
            "body": "one",
            "process_immediately": True,
        },
    )
    assert r1.status_code == 201
    sent_after_first = len(mock.sent)

    # Second send should be rate-limited / deferred (still queued or eventually sent after process)
    r2 = await client.post(
        "/api/v1/messages/send",
        headers=h,
        json={
            "channel": "sms",
            "to_address": "+989120000003",
            "body": "two",
            "process_immediately": True,
        },
    )
    assert r2.status_code == 201
    # Either deferred (no additional provider send) or limiter recorded deferral
    assert get_rate_limiter().deferrals >= 1 or len(mock.sent) == sent_after_first


@pytest.mark.asyncio
async def test_multi_provider_disable_failover(client: AsyncClient):
    reset_provider_registry_for_tests()
    h = tenant_headers(uuid.uuid4())
    await _mock_provider(client, h, name="P1", priority=1, force_fail=True)
    await _mock_provider(client, h, name="P2", priority=2, force_fail=True)
    await _mock_provider(client, h, name="P3", priority=3, force_fail=False)

    send = await client.post(
        "/api/v1/messages/send",
        headers=h,
        json={
            "channel": "sms",
            "to_address": "+989120000004",
            "body": "cascade",
            "process_immediately": True,
        },
    )
    assert send.status_code == 201
    assert send.json()[0]["status"] == "sent"
    events = get_event_publisher().published
    failovers = [
        e for e in events if e.event_type == CommunicationEventType.PROVIDER_FAILOVER.value
    ]
    assert len(failovers) >= 1


@pytest.mark.asyncio
async def test_health_ready_and_metrics(client: AsyncClient):
    ready = await client.get("/health/ready")
    assert ready.status_code == 200
    assert ready.json()["database"] == "up"

    metrics_root = await client.get("/metrics")
    assert metrics_root.status_code == 200
    assert "idempotency" in metrics_root.json()["features"]

    h = tenant_headers(uuid.uuid4())
    await _mock_provider(client, h, name="M", priority=1)
    tenant_metrics = await client.get("/api/v1/monitoring/metrics", headers=h)
    assert tenant_metrics.status_code == 200
    body = tenant_metrics.json()
    assert "provider_delivery" in body
    assert "rate_limit_deferrals" in body
    assert "queue_by_status" in body


@pytest.mark.asyncio
async def test_capabilities_include_sports_and_idempotency(client: AsyncClient):
    res = await client.get("/capabilities")
    body = res.json()
    assert "sports" in body["contact_source_types"]
    assert "idempotency" in body["features"]
    assert "metrics" in body["features"]


@pytest.mark.asyncio
async def test_otp_rejects_core_reserved_purpose(client: AsyncClient):
    h = tenant_headers(uuid.uuid4())
    await _mock_provider(client, h, name="OTP", priority=1)
    res = await client.post(
        "/api/v1/otp/request",
        headers=h,
        json={
            "destination": "+989120000005",
            "channel": "sms",
            "purpose": "auth.login",
        },
    )
    assert res.status_code == 422
    assert res.json()["error"]["code"] == "otp_purpose_reserved"


@pytest.mark.asyncio
async def test_sports_contact_source(client: AsyncClient):
    h = tenant_headers(uuid.uuid4())
    res = await client.post(
        "/api/v1/contacts/sources",
        headers=h,
        json={
            "name": "Sports API",
            "source_type": "sports",
            "base_url": "http://sports.local",
            "auth_config": {
                "mock_contacts": [{"id": "1", "phone": "+989120000006", "display_name": "Athlete"}]
            },
            "field_mapping": {"phone": "phone", "display_name": "display_name", "id": "id"},
        },
    )
    assert res.status_code == 201
    sid = res.json()["id"]
    resolved = await client.post(
        "/api/v1/contacts/resolve",
        headers=h,
        json={"source_id": sid, "channel": "sms"},
    )
    assert resolved.status_code == 200
    assert resolved.json()[0]["source_type"] == "sports"


@pytest.mark.asyncio
async def test_concurrent_queue_process_no_crash(client: AsyncClient):
    h = tenant_headers(uuid.uuid4())
    await _mock_provider(client, h, name="C", priority=1)
    for i in range(5):
        await client.post(
            "/api/v1/messages/send",
            headers=h,
            json={
                "channel": "sms",
                "to_address": f"+98912000001{i}",
                "body": f"m{i}",
                "process_immediately": False,
            },
        )

    async def _process():
        return await client.post("/api/v1/messages/queue/process?limit=10", headers=h)

    results = await asyncio.gather(*[_process() for _ in range(3)])
    assert all(r.status_code == 200 for r in results)


@pytest.mark.asyncio
async def test_permission_helper_denies_without_role(monkeypatch):
    from app.api.permissions import user_has_permission
    from shared.security import CurrentUser

    user = CurrentUser(user_id="u1", username="x", roles=["viewer"])
    assert user_has_permission(user, "communication.messages.send") is False
    admin = CurrentUser(user_id="a1", username="a", roles=["tenant_admin"])
    assert user_has_permission(admin, "communication.messages.send") is True


def test_otp_isolation_documented():
    from pathlib import Path

    text = Path(__file__).resolve().parents[1].joinpath("services/otp_service.py").read_text(
        encoding="utf-8"
    )
    assert "Core Platform auth OTP" in text
    assert "ADR-012" in text


def test_migration_0002_exists():
    from pathlib import Path

    versions = Path(__file__).resolve().parents[2] / "alembic" / "versions"
    assert (versions / "0002_validation_hardening.py").exists()
