"""Tests for Phase 11.F (notification preferences + retry) and 11.I (per-user locale)."""

from __future__ import annotations

from typing import Any

import pytest

from simorgh.apps.notifications import channels as channels_module
from simorgh.apps.notifications.models import (
    Notification,
    NotificationDeliveryStatus,
    NotificationPreference,
)
from simorgh.apps.notifications.services import (
    dispatch,
    is_channel_enabled,
    retry_failed,
    set_preference,
)
from simorgh.apps.notifications.templates import (
    NotificationTemplate,
    register_template,
)
from simorgh.core.locale import get_user_locale, set_user_locale

TEMPLATE_KIND = "test.phase11.preference_demo"


@pytest.fixture(autouse=True)
def _register_template():
    template = NotificationTemplate(
        kind=TEMPLATE_KIND,
        title_key="notifications.phase11.title",
        body_key="notifications.phase11.body",
        default_channels=("inbox", "email"),
    )
    register_template(template)


@pytest.fixture
def org_node(acme_tree):
    return acme_tree["root"]


# ---------------------------------------------------------------------------
# Preferences
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_default_is_enabled(tenant_acme, alice):
    assert is_channel_enabled(
        tenant_id=tenant_acme.pk, user_id=alice.pk, kind=TEMPLATE_KIND, channel="email"
    )


@pytest.mark.django_db
def test_disabling_preference_blocks_delivery(tenant_acme, org_node, alice):
    set_preference(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        user_id=alice.pk,
        kind=TEMPLATE_KIND,
        channel="email",
        enabled=False,
    )
    created = dispatch(
        TEMPLATE_KIND,
        recipients=[alice],
        context={"name": "A"},
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
    )
    channels_used = {n.channel for n in created}
    assert channels_used == {"inbox"}  # email suppressed
    assert NotificationPreference.objects.count() == 1


@pytest.mark.django_db
def test_dispatch_marks_delivered_status(tenant_acme, org_node, alice):
    created = dispatch(
        TEMPLATE_KIND,
        recipients=[alice],
        context={"name": "A"},
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
    )
    assert all(n.status == NotificationDeliveryStatus.DELIVERED for n in created)
    assert all(n.attempt == 1 for n in created)


# ---------------------------------------------------------------------------
# Retry
# ---------------------------------------------------------------------------


class _ExplodingChannel:
    name = "email"
    calls = 0

    def send(self, notification: Any) -> None:
        type(self).calls += 1
        raise RuntimeError("smtp down")


@pytest.mark.django_db
def test_failed_delivery_records_status_and_retries(
    tenant_acme, org_node, alice, monkeypatch
):
    exploding = _ExplodingChannel()
    monkeypatch.setitem(channels_module._DEFAULT_BACKENDS, "email", exploding)

    created = dispatch(
        TEMPLATE_KIND,
        recipients=[alice],
        context={"name": "A"},
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
    )
    email_rows = [n for n in created if n.channel == "email"]
    assert len(email_rows) == 1
    assert email_rows[0].status == NotificationDeliveryStatus.FAILED
    assert email_rows[0].attempt == 1
    assert "smtp" in email_rows[0].last_error.lower()

    # retry_failed picks it back up
    retry_failed()
    email_rows[0].refresh_from_db()
    assert email_rows[0].attempt == 2


@pytest.mark.django_db
def test_retry_marks_dead_after_max_attempts(
    tenant_acme, org_node, alice, monkeypatch
):
    exploding = _ExplodingChannel()
    monkeypatch.setitem(channels_module._DEFAULT_BACKENDS, "email", exploding)

    dispatch(
        TEMPLATE_KIND,
        recipients=[alice],
        context={"name": "A"},
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        channels=["email"],
    )
    row = Notification.objects.get(recipient=alice, channel="email")
    # max_attempts default is 3 → need two more retries
    retry_failed()
    retry_failed()
    row.refresh_from_db()
    assert row.status == NotificationDeliveryStatus.DEAD
    assert row.attempt == 3


# ---------------------------------------------------------------------------
# Per-user locale
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_get_user_locale_defaults_to_system(alice):
    assert get_user_locale(alice) in {"en", "fa"}  # whatever LANGUAGE_CODE is


@pytest.mark.django_db
def test_set_and_read_user_locale(alice):
    set_user_locale(alice, "fa")
    assert get_user_locale(alice) == "fa"


@pytest.mark.django_db
def test_dispatch_renders_in_user_locale(tenant_acme, org_node, alice):
    set_user_locale(alice, "fa")
    created = dispatch(
        TEMPLATE_KIND,
        recipients=[alice],
        context={},
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        channels=["inbox"],
    )
    # gettext falls back to the key when no catalog entry exists, but the
    # dispatch path must not raise when a non-default locale is requested.
    assert created[0].title == "notifications.phase11.title"
