"""INDL-38 Service Scheduling Phase 2: Family Notifications — service-layer tests.

Runs against the FastAPI app + the indelis_test Postgres DB (see conftest.py).
The ARQ pool is a plain AsyncMock — no real Redis/worker is required, since
these tests exercise `SchedulingService.create()`/`update()` directly rather
than going through the HTTP layer.

Spec: docs/38_INDL-38_Service_Scheduling_Phase2_Notifications.md
Covers: AC-01, AC-03, AC-05, AC-09, AC-10, AC-11.
"""
import datetime as dt
from unittest.mock import AsyncMock

import pytest
import pytest_asyncio
from sqlalchemy import select

from src.apps.auth.models.user import User
from src.apps.scheduling.models.service_event import ServiceEvent
from src.apps.scheduling.schemas.scheduling import ServiceCreate, ServiceUpdate
from src.apps.scheduling.services.scheduling_service import SchedulingService
from src.core.security import hash_password


class _FakeJob:
    def __init__(self, job_id: str):
        self.job_id = job_id


def _make_arq_mock(job_id: str = "arq-job-123"):
    mock = AsyncMock()
    mock.enqueue_job = AsyncMock(return_value=_FakeJob(job_id))
    return mock


@pytest_asyncio.fixture
async def staff_user(db_session, test_account):
    user = User(
        tenant_id=test_account.id,
        email="staff-notify@testcemetery.com",
        password_hash=hash_password("TestPassword123"),
        first_name="Staff",
        last_name="User",
        role="staff",
        status="active",
    )
    db_session.add(user)
    await db_session.flush()
    return user


def _future_create_payload(**overrides) -> ServiceCreate:
    data = {
        "service_type": "Burial",
        "date": (dt.date.today() + dt.timedelta(days=10)),
        "start_time": dt.time(10, 0),
        "family_contact_email": "family@example.com",
        "status": "awaiting_family_confirm",
    }
    data.update(overrides)
    return ServiceCreate(**data)


async def _fetch_event(db_session, service_id) -> ServiceEvent:
    return (
        await db_session.execute(select(ServiceEvent).where(ServiceEvent.id == service_id))
    ).scalar_one()


# ── AC-01 / AC-03: create enqueues confirmation + schedules reminder ────────

@pytest.mark.asyncio
async def test_create_awaiting_confirm_enqueues_confirmation_and_reminder(
    db_session, test_account, staff_user
):
    arq = _make_arq_mock()
    service = SchedulingService(db_session)
    payload = _future_create_payload(
        notify_family_confirmation=True, notify_family_reminder_24h=True
    )

    detail = await service.create(test_account.id, payload, staff_user, arq_redis=arq)

    enqueued = [c.args[0] for c in arq.enqueue_job.call_args_list]
    assert "send_scheduling_confirmation" in enqueued
    assert "send_scheduling_reminder" in enqueued

    reminder_call = next(
        c for c in arq.enqueue_job.call_args_list if c.args[0] == "send_scheduling_reminder"
    )
    assert "_defer_until" in reminder_call.kwargs
    expected_defer = dt.datetime.combine(
        payload.date, payload.start_time, tzinfo=dt.timezone.utc
    ) - dt.timedelta(hours=24)
    assert reminder_call.kwargs["_defer_until"] == expected_defer

    event = await _fetch_event(db_session, detail["id"])
    assert event.reminder_job_id == "arq-job-123"


@pytest.mark.asyncio
async def test_create_confirmation_only_does_not_schedule_reminder(
    db_session, test_account, staff_user
):
    arq = _make_arq_mock()
    service = SchedulingService(db_session)
    payload = _future_create_payload(
        notify_family_confirmation=True, notify_family_reminder_24h=False
    )

    detail = await service.create(test_account.id, payload, staff_user, arq_redis=arq)

    enqueued = [c.args[0] for c in arq.enqueue_job.call_args_list]
    assert enqueued == ["send_scheduling_confirmation"]
    event = await _fetch_event(db_session, detail["id"])
    assert event.reminder_job_id is None


# ── AC-09 / AC-10: draft services and unset flags never enqueue anything ────

@pytest.mark.asyncio
async def test_create_draft_with_flags_true_never_enqueues(db_session, test_account, staff_user):
    arq = _make_arq_mock()
    service = SchedulingService(db_session)
    payload = _future_create_payload(
        status="draft", notify_family_confirmation=True, notify_family_reminder_24h=True
    )

    await service.create(test_account.id, payload, staff_user, arq_redis=arq)

    arq.enqueue_job.assert_not_called()


@pytest.mark.asyncio
async def test_create_awaiting_confirm_with_flags_false_never_enqueues(
    db_session, test_account, staff_user
):
    arq = _make_arq_mock()
    service = SchedulingService(db_session)
    payload = _future_create_payload(
        notify_family_confirmation=False, notify_family_reminder_24h=False
    )

    await service.create(test_account.id, payload, staff_user, arq_redis=arq)

    arq.enqueue_job.assert_not_called()


# ── AC-05: update cancels + re-enqueues the reminder on date/time change ────

def _update_payload_from(event: ServiceEvent, **overrides) -> ServiceUpdate:
    data = {
        "service_type": event.service_type,
        "decedent_name": event.decedent_name,
        "record_id": event.record_id,
        "date": event.scheduled_date,
        "start_time": event.scheduled_time,
        "duration_minutes": event.duration_minutes,
        "officiant": event.officiant,
        "family_contact_name": event.family_contact_name,
        "family_contact_phone": event.family_contact_phone,
        "family_contact_email": event.family_contact_email,
        "expected_attendees": event.expected_attendees,
        "event_note": event.event_note,
        "plot_location": event.plot_location,
        "section": event.section,
        "equipment_needed": event.equipment_needed,
        "notes_for_crew": event.notes,
        "notify_family_confirmation": event.notify_family_confirmation,
        "notify_family_reminder_24h": event.notify_family_reminder_24h,
    }
    data.update(overrides)
    return ServiceUpdate(**data)


@pytest.mark.asyncio
async def test_update_datetime_change_cancels_old_reminder_and_reschedules(
    db_session, test_account, staff_user, monkeypatch
):
    arq = _make_arq_mock(job_id="job-original")
    service = SchedulingService(db_session)
    payload = _future_create_payload(notify_family_reminder_24h=True)
    detail = await service.create(test_account.id, payload, staff_user, arq_redis=arq)

    aborted_job_ids = []

    async def _fake_abort(self):
        aborted_job_ids.append(self.job_id)
        return True

    monkeypatch.setattr("src.apps.scheduling.services.scheduling_service.ArqJob.abort", _fake_abort)

    event = await _fetch_event(db_session, detail["id"])
    assert event.reminder_job_id == "job-original"

    arq2 = _make_arq_mock(job_id="job-rescheduled")
    new_date = payload.date + dt.timedelta(days=2)
    update_payload = _update_payload_from(event, date=new_date)

    await service.update(test_account.id, detail["id"], update_payload, staff_user, arq_redis=arq2)

    assert aborted_job_ids == ["job-original"]
    reminder_calls = [
        c for c in arq2.enqueue_job.call_args_list if c.args[0] == "send_scheduling_reminder"
    ]
    assert len(reminder_calls) == 1
    expected_defer = dt.datetime.combine(
        new_date, payload.start_time, tzinfo=dt.timezone.utc
    ) - dt.timedelta(hours=24)
    assert reminder_calls[0].kwargs["_defer_until"] == expected_defer

    event = await _fetch_event(db_session, detail["id"])
    assert event.reminder_job_id == "job-rescheduled"


@pytest.mark.asyncio
async def test_update_turning_off_reminder_flag_cancels_job(
    db_session, test_account, staff_user, monkeypatch
):
    arq = _make_arq_mock(job_id="job-to-cancel")
    service = SchedulingService(db_session)
    payload = _future_create_payload(notify_family_reminder_24h=True)
    detail = await service.create(test_account.id, payload, staff_user, arq_redis=arq)

    aborted_job_ids = []

    async def _fake_abort(self):
        aborted_job_ids.append(self.job_id)
        return True

    monkeypatch.setattr("src.apps.scheduling.services.scheduling_service.ArqJob.abort", _fake_abort)

    event = await _fetch_event(db_session, detail["id"])
    arq2 = _make_arq_mock()
    update_payload = _update_payload_from(event, notify_family_reminder_24h=False)

    await service.update(test_account.id, detail["id"], update_payload, staff_user, arq_redis=arq2)

    assert aborted_job_ids == ["job-to-cancel"]
    arq2.enqueue_job.assert_not_called()
    event = await _fetch_event(db_session, detail["id"])
    assert event.reminder_job_id is None
