"""Tests for S1.3 Calendar & Scheduling.

Covers:
- Model creation and validation
- Service-layer business logic (create, update, delete, attendees)
- Selector queries (date-range, tenant isolation, linked events)
- API endpoints: list, create, detail, update, delete, attendees
- Permission registration
"""

from __future__ import annotations

from datetime import datetime, timezone

import pytest

from simorgh.apps.accounts.models import User
from simorgh.apps.calendar.models import CalendarEvent, EventAttendee
from simorgh.apps.calendar import services, selectors
from simorgh.apps.organizations.services import create_node
from simorgh.apps.tenants.models import Tenant


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def tenant(db) -> Tenant:
    return Tenant.objects.create(slug="cal-test", name="CalTest")


@pytest.fixture
def org_node(tenant):
    return create_node(tenant_id=tenant.pk, name="HQ")


@pytest.fixture
def user(db) -> User:
    return User.objects.create_user("+989222222222", password="x", email="caluser@example.com")


@pytest.fixture
def user2(db) -> User:
    return User.objects.create_user("+989333333333", password="x", email="caluser2@example.com")


@pytest.fixture
def event(tenant, org_node, user) -> CalendarEvent:
    return services.create_event(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        owner_id=user.pk,
        title="Team Meeting",
        start_dt=datetime(2025, 6, 1, 10, 0, tzinfo=timezone.utc),
        end_dt=datetime(2025, 6, 1, 11, 0, tzinfo=timezone.utc),
        location="Conference Room A",
    )


@pytest.fixture
def api_client():
    from rest_framework.test import APIClient
    return APIClient()


@pytest.fixture
def authed_client(api_client, user, tenant):
    api_client.force_login(user)
    return api_client, tenant.slug


# ---------------------------------------------------------------------------
# Service layer tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_create_event_basic(tenant, org_node, user):
    ev = services.create_event(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        owner_id=user.pk,
        title="Project Kickoff",
        start_dt=datetime(2025, 6, 1, 9, 0, tzinfo=timezone.utc),
        end_dt=datetime(2025, 6, 1, 10, 0, tzinfo=timezone.utc),
    )
    assert ev.pk is not None
    assert ev.title == "Project Kickoff"
    assert ev.tenant_id == tenant.pk
    assert ev.owner_id == user.pk
    assert ev.public_id is not None


@pytest.mark.django_db
def test_create_event_validates_end_after_start(tenant, org_node, user):
    from django.core.exceptions import ValidationError

    with pytest.raises(ValidationError, match="end_dt must be after start_dt"):
        services.create_event(
            tenant_id=tenant.pk,
            organization_node_id=org_node.pk,
            owner_id=user.pk,
            title="Bad Event",
            start_dt=datetime(2025, 6, 1, 11, 0, tzinfo=timezone.utc),
            end_dt=datetime(2025, 6, 1, 10, 0, tzinfo=timezone.utc),
        )


@pytest.mark.django_db
def test_create_event_all_day_skips_time_validation(tenant, org_node, user):
    """All-day events don't require end_dt > start_dt (same-day is fine)."""
    ev = services.create_event(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        owner_id=user.pk,
        title="All Day Event",
        start_dt=datetime(2025, 6, 1, 0, 0, tzinfo=timezone.utc),
        end_dt=datetime(2025, 6, 1, 0, 0, tzinfo=timezone.utc),
        all_day=True,
    )
    assert ev.all_day is True


@pytest.mark.django_db
def test_update_event_fields(event):
    updated = services.update_event(event, title="Renamed Meeting", location="Room B")
    assert updated.title == "Renamed Meeting"
    assert updated.location == "Room B"
    event.refresh_from_db()
    assert event.title == "Renamed Meeting"


@pytest.mark.django_db
def test_update_event_validates_end_after_start(event):
    from django.core.exceptions import ValidationError

    with pytest.raises(ValidationError, match="end_dt must be after start_dt"):
        services.update_event(
            event,
            end_dt=datetime(2025, 6, 1, 9, 0, tzinfo=timezone.utc),  # before start_dt
        )


@pytest.mark.django_db
def test_delete_event(event):
    ev_pk = event.pk
    services.delete_event(event)
    assert not CalendarEvent.objects.filter(pk=ev_pk).exists()


@pytest.mark.django_db
def test_add_attendee_user(event, user2):
    attendee = services.add_attendee(event, user_id=user2.pk)
    assert attendee.pk is not None
    assert attendee.user_id == user2.pk
    assert attendee.status == "pending"


@pytest.mark.django_db
def test_add_attendee_idempotent(event, user2):
    a1 = services.add_attendee(event, user_id=user2.pk)
    a2 = services.add_attendee(event, user_id=user2.pk)
    assert a1.pk == a2.pk
    assert EventAttendee.objects.filter(event=event, user_id=user2.pk).count() == 1


@pytest.mark.django_db
def test_remove_attendee(event, user2):
    services.add_attendee(event, user_id=user2.pk)
    services.remove_attendee(event, user_id=user2.pk)
    assert not EventAttendee.objects.filter(event=event, user_id=user2.pk).exists()


# ---------------------------------------------------------------------------
# Selector tests
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_selector_events_for_range(tenant, org_node, user):
    services.create_event(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        owner_id=user.pk,
        title="June Meeting",
        start_dt=datetime(2025, 6, 15, 10, 0, tzinfo=timezone.utc),
        end_dt=datetime(2025, 6, 15, 11, 0, tzinfo=timezone.utc),
    )
    results = selectors.get_events_for_range(
        tenant_id=tenant.pk,
        user_id=user.pk,
        from_dt=datetime(2025, 6, 1, tzinfo=timezone.utc),
        to_dt=datetime(2025, 7, 1, tzinfo=timezone.utc),
    )
    assert results.count() == 1
    assert results.first().title == "June Meeting"


@pytest.mark.django_db
def test_selector_events_range_excludes_out_of_range(tenant, org_node, user):
    # Create an event in July
    services.create_event(
        tenant_id=tenant.pk,
        organization_node_id=org_node.pk,
        owner_id=user.pk,
        title="July Meeting",
        start_dt=datetime(2025, 7, 15, 10, 0, tzinfo=timezone.utc),
        end_dt=datetime(2025, 7, 15, 11, 0, tzinfo=timezone.utc),
    )
    results = selectors.get_events_for_range(
        tenant_id=tenant.pk,
        user_id=user.pk,
        from_dt=datetime(2025, 6, 1, tzinfo=timezone.utc),
        to_dt=datetime(2025, 7, 1, tzinfo=timezone.utc),
    )
    assert results.count() == 0


@pytest.mark.django_db
def test_selector_tenant_isolation(tenant, org_node, user):
    other = Tenant.objects.create(slug="other-cal", name="Other")
    other_node = create_node(tenant_id=other.pk, name="Root")
    other_user = User.objects.create_user("+989444444444", password="x", email="otheruser@example.com")

    services.create_event(
        tenant_id=other.pk,
        organization_node_id=other_node.pk,
        owner_id=other_user.pk,
        title="Other Tenant Event",
        start_dt=datetime(2025, 6, 15, 10, 0, tzinfo=timezone.utc),
        end_dt=datetime(2025, 6, 15, 11, 0, tzinfo=timezone.utc),
    )

    results = selectors.get_events_for_range(
        tenant_id=tenant.pk,
        user_id=user.pk,
        from_dt=datetime(2025, 6, 1, tzinfo=timezone.utc),
        to_dt=datetime(2025, 7, 1, tzinfo=timezone.utc),
    )
    assert results.count() == 0


# ---------------------------------------------------------------------------
# Permission registration test
# ---------------------------------------------------------------------------

@pytest.mark.django_db
def test_permissions_registered():
    from simorgh.apps.iam.registry import sync_registry_to_db
    from simorgh.apps.iam.models import Permission

    sync_registry_to_db()

    expected = [
        "calendar.event.view",
        "calendar.event.create",
        "calendar.event.update",
        "calendar.event.delete",
        "calendar.event.manage_others",
    ]
    for codename in expected:
        assert Permission.objects.filter(codename=codename).exists(), f"Missing permission: {codename}"


# ---------------------------------------------------------------------------
# API tests
# ---------------------------------------------------------------------------

FROM_DT = "2025-06-01T00:00:00"
TO_DT = "2025-07-01T00:00:00"


@pytest.mark.django_db
def test_api_event_list_requires_date_range(authed_client):
    client, slug = authed_client
    resp = client.get("/api/v1/calendar/events/", HTTP_X_TENANT=slug)
    assert resp.status_code == 400
    assert "from" in resp.json()["detail"].lower() or "to" in resp.json()["detail"].lower()


@pytest.mark.django_db
def test_api_event_list(authed_client, event):
    client, slug = authed_client
    resp = client.get(
        f"/api/v1/calendar/events/?from={FROM_DT}&to={TO_DT}",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    data = resp.json()
    assert isinstance(data, list)
    assert len(data) == 1
    assert data[0]["title"] == "Team Meeting"


@pytest.mark.django_db
def test_api_event_create(authed_client, org_node):
    client, slug = authed_client
    payload = {
        "title": "API Created Event",
        "start_dt": "2025-06-10T14:00:00",
        "end_dt": "2025-06-10T15:00:00",
        "location": "Zoom",
        "organization_node_id": org_node.pk,
    }
    resp = client.post("/api/v1/calendar/events/", payload, format="json", HTTP_X_TENANT=slug)
    assert resp.status_code == 201
    data = resp.json()
    assert data["title"] == "API Created Event"
    assert data["location"] == "Zoom"


@pytest.mark.django_db
def test_api_event_create_validates_end_after_start(authed_client, org_node):
    client, slug = authed_client
    payload = {
        "title": "Bad Event",
        "start_dt": "2025-06-10T15:00:00",
        "end_dt": "2025-06-10T14:00:00",
        "organization_node_id": org_node.pk,
    }
    resp = client.post("/api/v1/calendar/events/", payload, format="json", HTTP_X_TENANT=slug)
    assert resp.status_code == 400


@pytest.mark.django_db
def test_api_event_detail(authed_client, event):
    client, slug = authed_client
    resp = client.get(f"/api/v1/calendar/events/{event.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 200
    data = resp.json()
    assert data["public_id"] == str(event.public_id)
    assert "attendees" in data


@pytest.mark.django_db
def test_api_event_update(authed_client, event):
    client, slug = authed_client
    resp = client.patch(
        f"/api/v1/calendar/events/{event.public_id}/",
        {"title": "Updated Title", "location": "New Location"},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 200
    assert resp.json()["title"] == "Updated Title"
    assert resp.json()["location"] == "New Location"


@pytest.mark.django_db
def test_api_event_delete(authed_client, event):
    client, slug = authed_client
    resp = client.delete(f"/api/v1/calendar/events/{event.public_id}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 204
    assert not CalendarEvent.objects.filter(pk=event.pk).exists()


@pytest.mark.django_db
def test_api_event_not_found_returns_404(authed_client):
    client, slug = authed_client
    import uuid
    resp = client.get(f"/api/v1/calendar/events/{uuid.uuid4()}/", HTTP_X_TENANT=slug)
    assert resp.status_code == 404


@pytest.mark.django_db
def test_api_attendee_add(authed_client, event, user2):
    client, slug = authed_client
    resp = client.post(
        f"/api/v1/calendar/events/{event.public_id}/attendees/",
        {"user_id": user2.pk},
        format="json",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 201
    data = resp.json()
    assert data["user_id"] == user2.pk
    assert data["status"] == "pending"


@pytest.mark.django_db
def test_api_attendee_remove(authed_client, event, user2):
    services.add_attendee(event, user_id=user2.pk)
    client, slug = authed_client
    resp = client.delete(
        f"/api/v1/calendar/events/{event.public_id}/attendees/{user2.pk}/",
        HTTP_X_TENANT=slug,
    )
    assert resp.status_code == 204
    assert not EventAttendee.objects.filter(event=event, user_id=user2.pk).exists()


@pytest.mark.django_db
def test_api_events_unauthenticated_returns_401(tenant):
    from rest_framework.test import APIClient
    client = APIClient()
    resp = client.get(
        f"/api/v1/calendar/events/?from={FROM_DT}&to={TO_DT}",
        HTTP_X_TENANT=tenant.slug,
    )
    assert resp.status_code == 401
