"""INDL-38 — worker job-body tests for the family notification emails.

Complements ``test_scheduling_notifications.py`` (which covers the service-layer
*enqueue* decisions). These tests exercise the ARQ job functions themselves —
``send_scheduling_confirmation`` / ``send_scheduling_reminder`` — asserting the
actual dispatch, the empty/invalid-email guard, and the draft / deleted /
flag-off skips (AC-06, AC-07, AC-08, AC-09).

The jobs open their OWN session via ``arq_app.AsyncSessionLocal`` and commit, so
these tests seed rows in a committed transaction on the ``indelis_test`` DB,
monkeypatch that sessionmaker to point at the test engine, stub the external
email dispatcher, and clean up every seeded tenant on teardown.
"""
import datetime as dt
import types
from unittest.mock import AsyncMock
from uuid import uuid4

import pytest
import pytest_asyncio
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

import src.core.email_dispatch as email_dispatch_mod
import src.worker.arq_app as arq_app
from src.apps.scheduling.models.scheduling_activity_log import SchedulingActivityLog
from src.apps.scheduling.models.service_event import ServiceEvent
from src.apps.tenants.models.account import Account
from src.core.config import settings
from src.worker.arq_app import send_scheduling_confirmation, send_scheduling_reminder

# Same test DB the shared conftest fixtures use.
_TEST_DATABASE_URL = settings.DATABASE_URL.replace("/indelis", "/indelis_test")
if not _TEST_DATABASE_URL.startswith("postgresql+psycopg://"):
    _TEST_DATABASE_URL = "postgresql+psycopg://" + _TEST_DATABASE_URL.split("://", 1)[-1]

_engine = create_async_engine(_TEST_DATABASE_URL, echo=False)
_Session = async_sessionmaker(_engine, class_=AsyncSession, expire_on_commit=False)


def _sent_result():
    """Minimal stand-in for EmailDispatchResult — the job reads .sent / .skipped_reason."""
    return types.SimpleNamespace(sent=True, skipped_reason=None)


@pytest_asyncio.fixture
async def seed():
    """Factory that commits an Account + ServiceEvent and cleans them up afterward."""
    tenant_ids: list = []

    async def _make(**overrides) -> dict:
        async with _Session() as s:
            account = Account(
                organization_name="Notify Cemetery",
                subdomain=f"notify-{uuid4().hex[:10]}",
                contact_email="office@notify.example.com",
                plan="starter",
                status="active",
            )
            s.add(account)
            await s.flush()

            fields = {
                "service_type": "interment",
                "decedent_name": "Jane Doe",
                "scheduled_date": dt.date.today() + dt.timedelta(days=5),
                "scheduled_time": dt.time(10, 0),
                "duration_minutes": 60,
                "plot_location": "A-101",
                "section": "A",
                "officiant": "Fr. Daniel Murphy",
                "family_contact_name": "Family A",
                "family_contact_email": "family@example.com",
                "notify_family_confirmation": True,
                "notify_family_reminder_24h": True,
                "status": "awaiting_family_confirm",
            }
            fields.update(overrides)
            is_deleted = fields.pop("_deleted", False)
            event = ServiceEvent(tenant_id=account.id, **fields)
            s.add(event)
            await s.flush()
            if is_deleted:
                event.deleted_at = dt.datetime.now(dt.timezone.utc)
            await s.commit()
            tenant_ids.append(account.id)
            return {"tenant_id": account.id, "service_id": event.id}

    yield _make

    async with _Session() as s:
        for tid in tenant_ids:
            # accounts CASCADE to services + activity logs, but delete children
            # first to stay robust regardless of FK config.
            await s.execute(delete(SchedulingActivityLog).where(SchedulingActivityLog.tenant_id == tid))
            await s.execute(delete(ServiceEvent).where(ServiceEvent.tenant_id == tid))
            await s.execute(delete(Account).where(Account.id == tid))
        await s.commit()


@pytest.fixture
def patch_jobs(monkeypatch):
    """Point the jobs at the test DB and stub the external email dispatcher."""
    monkeypatch.setattr(arq_app, "AsyncSessionLocal", _Session)
    send_mock = AsyncMock(return_value=_sent_result())
    monkeypatch.setattr(email_dispatch_mod.email_dispatch_service, "send", send_mock)
    return send_mock


async def _log_event_types(service_id) -> list:
    async with _Session() as s:
        rows = (
            await s.execute(
                select(SchedulingActivityLog.event_type).where(
                    SchedulingActivityLog.service_id == service_id
                )
            )
        ).scalars().all()
    return list(rows)


# ── Confirmation job ─────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_confirmation_job_sends_and_logs(seed, patch_jobs):
    rec = await seed()
    result = await send_scheduling_confirmation({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result["status"] == "sent"
    patch_jobs.assert_awaited_once()
    assert patch_jobs.call_args.kwargs["to"] == "family@example.com"
    assert "confirmation_email_sent" in await _log_event_types(rec["service_id"])


@pytest.mark.asyncio
async def test_confirmation_job_skips_empty_email_and_logs_error(seed, patch_jobs):
    rec = await seed(family_contact_email=None)
    result = await send_scheduling_confirmation({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result == {"status": "skipped", "reason": "invalid_email"}
    patch_jobs.assert_not_awaited()
    assert "email_error" in await _log_event_types(rec["service_id"])


@pytest.mark.asyncio
async def test_confirmation_job_skips_invalid_email(seed, patch_jobs):
    rec = await seed(family_contact_email="not-an-email")
    result = await send_scheduling_confirmation({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result == {"status": "skipped", "reason": "invalid_email"}
    patch_jobs.assert_not_awaited()


@pytest.mark.asyncio
async def test_confirmation_job_skips_draft(seed, patch_jobs):
    rec = await seed(status="draft")
    result = await send_scheduling_confirmation({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result == {"status": "skipped", "reason": "draft_status"}
    patch_jobs.assert_not_awaited()


@pytest.mark.asyncio
async def test_confirmation_job_skips_deleted_service(seed, patch_jobs):
    rec = await seed(_deleted=True)
    result = await send_scheduling_confirmation({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result == {"status": "skipped", "reason": "not_found_or_deleted"}
    patch_jobs.assert_not_awaited()


# ── Reminder job ─────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_reminder_job_sends_and_logs(seed, patch_jobs):
    rec = await seed()
    result = await send_scheduling_reminder({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result["status"] == "sent"
    patch_jobs.assert_awaited_once()
    assert patch_jobs.call_args.kwargs["to"] == "family@example.com"
    assert "reminder_email_sent" in await _log_event_types(rec["service_id"])


@pytest.mark.asyncio
async def test_reminder_job_skips_when_flag_off(seed, patch_jobs):
    # AC-05 safety net: flag turned off after the job was scheduled.
    rec = await seed(notify_family_reminder_24h=False)
    result = await send_scheduling_reminder({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result == {"status": "skipped", "reason": "reminder_disabled"}
    patch_jobs.assert_not_awaited()


@pytest.mark.asyncio
async def test_reminder_job_skips_invalid_email_and_logs_error(seed, patch_jobs):
    rec = await seed(family_contact_email="")
    result = await send_scheduling_reminder({}, str(rec["service_id"]), str(rec["tenant_id"]))

    assert result == {"status": "skipped", "reason": "invalid_email"}
    patch_jobs.assert_not_awaited()
    assert "email_error" in await _log_event_types(rec["service_id"])
