"""Phase 13.1 — Appointment Engine tests."""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

import pytest

from app.events.publisher import get_event_publisher
from app.events.types import HealthcareEventType
from app.providers.mocks import MockCommunicationProvider
from app.tests.conftest import TENANT_A, TENANT_B, tenant_headers
from app.validators.appointments import ensure_appointment_lifecycle_transition
from app.models.types import AppointmentLifecycleAction, AppointmentStatus
from shared.exceptions import AppError


async def _seed_foundation(client):
    headers = tenant_headers(TENANT_A)
    clinic = await client.post(
        "/api/v1/clinics",
        headers=headers,
        json={"code": "CLN131", "name": "Appt Clinic", "status": "active"},
    )
    assert clinic.status_code == 201
    clinic_id = clinic.json()["id"]
    doctor = await client.post(
        "/api/v1/doctors",
        headers=headers,
        json={
            "user_id": "doc-131",
            "clinic_id": clinic_id,
            "code": "DOC131",
            "display_name": "Dr Appt",
        },
    )
    assert doctor.status_code == 201
    patient = await client.post(
        "/api/v1/patients",
        headers=headers,
        json={
            "user_id": "pat-131",
            "code": "PAT131",
            "display_name": "Patient Appt",
            "contact": {"phone": "+989121111111"},
        },
    )
    assert patient.status_code == 201
    return clinic_id, doctor.json()["id"], patient.json()["id"]


def test_appointment_lifecycle_transition_unit():
    ensure_appointment_lifecycle_transition(
        action=AppointmentLifecycleAction.CONFIRM, current=AppointmentStatus.DRAFT
    )
    with pytest.raises(AppError) as exc:
        ensure_appointment_lifecycle_transition(
            action=AppointmentLifecycleAction.CONFIRM, current=AppointmentStatus.COMPLETED
        )
    assert exc.value.error_code == "appointment_terminal"


@pytest.mark.asyncio
async def test_appointment_lifecycle_and_reminder(client):
    MockCommunicationProvider.reset()
    clinic_id, doctor_id, patient_id = await _seed_foundation(client)
    headers = tenant_headers(TENANT_A)
    start = datetime(2026, 8, 1, 10, 0, tzinfo=timezone.utc)
    end = start + timedelta(minutes=30)

    appt_type = await client.post(
        "/api/v1/appointment-types",
        headers=headers,
        json={
            "clinic_id": clinic_id,
            "code": "CONSULT",
            "name": "Consultation",
            "default_duration_minutes": 30,
        },
    )
    assert appt_type.status_code == 201

    schedule = await client.post(
        "/api/v1/schedules",
        headers=headers,
        json={
            "clinic_id": clinic_id,
            "doctor_id": doctor_id,
            "code": "SCH-MON",
            "name": "Monday",
            "day_of_week": "monday",
            "start_time": "09:00:00",
            "end_time": "17:00:00",
        },
    )
    assert schedule.status_code == 201

    booked = await client.post(
        "/api/v1/appointments",
        headers=headers,
        json={
            "clinic_id": clinic_id,
            "doctor_id": doctor_id,
            "patient_id": patient_id,
            "appointment_type_id": appt_type.json()["id"],
            "code": "APT1",
            "scheduled_start": start.isoformat(),
            "scheduled_end": end.isoformat(),
        },
    )
    assert booked.status_code == 201, booked.text
    appt = booked.json()
    assert appt["status"] == "draft"
    aid = appt["id"]
    version = appt["version"]

    confirmed = await client.post(
        f"/api/v1/appointments/{aid}/confirm",
        headers=headers,
        json={"version": version},
    )
    assert confirmed.status_code == 200, confirmed.text
    assert confirmed.json()["status"] == "confirmed"
    assert confirmed.json()["reminder_scheduled"] is True
    assert len(MockCommunicationProvider.sent) == 1
    assert MockCommunicationProvider.sent[0]["template_key"] == "healthcare.appointment.reminder"

    events = {e.event_type for e in get_event_publisher().published}
    assert HealthcareEventType.APPOINTMENT_BOOKED.value in events
    assert HealthcareEventType.APPOINTMENT_CONFIRMED.value in events
    assert HealthcareEventType.APPOINTMENT_REMINDER_SCHEDULED.value in events


@pytest.mark.asyncio
async def test_appointment_conflict_detection(client):
    clinic_id, doctor_id, patient_id = await _seed_foundation(client)
    headers = tenant_headers(TENANT_A)
    start = datetime(2026, 8, 2, 14, 0, tzinfo=timezone.utc)
    end = start + timedelta(minutes=30)
    payload = {
        "clinic_id": clinic_id,
        "doctor_id": doctor_id,
        "patient_id": patient_id,
        "code": "APT-CONF-A",
        "scheduled_start": start.isoformat(),
        "scheduled_end": end.isoformat(),
    }
    first = await client.post("/api/v1/appointments", headers=headers, json=payload)
    assert first.status_code == 201

    overlap = await client.post(
        "/api/v1/appointments",
        headers=headers,
        json={
            **payload,
            "code": "APT-CONF-B",
            "scheduled_start": (start + timedelta(minutes=15)).isoformat(),
            "scheduled_end": (end + timedelta(minutes=15)).isoformat(),
        },
    )
    assert overlap.status_code == 409
    assert overlap.json()["error"]["code"] == "appointment_conflict"


@pytest.mark.asyncio
async def test_appointment_tenant_isolation(client):
    clinic_id, doctor_id, patient_id = await _seed_foundation(client)
    headers = tenant_headers(TENANT_A)
    start = datetime(2026, 8, 3, 9, 0, tzinfo=timezone.utc)
    booked = await client.post(
        "/api/v1/appointments",
        headers=headers,
        json={
            "clinic_id": clinic_id,
            "doctor_id": doctor_id,
            "patient_id": patient_id,
            "code": "APT-ISO",
            "scheduled_start": start.isoformat(),
            "scheduled_end": (start + timedelta(minutes=30)).isoformat(),
        },
    )
    assert booked.status_code == 201
    aid = booked.json()["id"]
    other = await client.get(
        f"/api/v1/appointments/{aid}",
        headers=tenant_headers(TENANT_B),
    )
    assert other.status_code == 404


@pytest.mark.asyncio
async def test_waiting_list_and_capabilities(client):
    clinic_id, doctor_id, patient_id = await _seed_foundation(client)
    headers = tenant_headers(TENANT_A)
    wl = await client.post(
        "/api/v1/waiting-list",
        headers=headers,
        json={
            "clinic_id": clinic_id,
            "doctor_id": doctor_id,
            "patient_id": patient_id,
            "code": "WL1",
            "priority": 50,
        },
    )
    assert wl.status_code == 201

    caps = await client.get("/capabilities")
    assert caps.json()["features"]["appointments"] is True
    assert caps.json()["features"]["waiting_list"] is True
