"""Notifications API views."""
from __future__ import annotations

from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from django.conf import settings

from simorgh.apps.notifications import selectors as sel
from simorgh.apps.notifications.models import (
    Notification,
    NotificationChannel,
    PushSubscription,
    UserQuietHours,
)
from simorgh.apps.notifications.registry import get_all_notification_types
from simorgh.apps.notifications.services import is_channel_enabled, set_preference


def _serialize_notification(n: Notification) -> dict:
    return {
        "id": n.pk,
        "public_id": str(n.public_id),
        "kind": n.kind,
        "channel": n.channel,
        "title": n.title,
        "body": n.body,
        "payload": n.payload,
        "is_read": n.read_at is not None,
        "read_at": n.read_at.isoformat() if n.read_at else None,
        "delivered_at": n.delivered_at.isoformat() if n.delivered_at else None,
        "created_at": n.created_at.isoformat(),
    }


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def notification_list(request: Request) -> Response:
    """GET /api/v1/notifications/

    Returns the current user's inbox notifications (newest first).
    Query params:
      unread_only=1  — only return unread notifications
      limit          — max rows (default 50, max 200)
    """
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)

    limit_raw = request.query_params.get("limit", "50")
    try:
        limit = min(int(limit_raw), 200)
    except (ValueError, TypeError):
        limit = 50

    unread_only = request.query_params.get("unread_only") == "1"
    if unread_only:
        qs = sel.list_unread_notifications(request.user.pk, tenant.pk)
    else:
        qs = sel.list_notifications(request.user.pk, tenant.pk, limit=limit)

    return Response({"results": [_serialize_notification(n) for n in qs]})


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def notification_count(request: Request) -> Response:
    """GET /api/v1/notifications/count/

    Returns ``{"unread": N}`` for badge display.
    """
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)
    return Response({"unread": sel.count_unread(request.user.pk, tenant.pk)})


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def notification_detail(request: Request, pk: int) -> Response:
    """GET /api/v1/notifications/<pk>/"""
    try:
        n = sel.get_notification(pk, request.user.pk)
    except Notification.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
    return Response(_serialize_notification(n))


@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def notification_delete(request: Request, pk: int) -> Response:
    """DELETE /api/v1/notifications/<pk>/"""
    try:
        n = sel.get_notification(pk, request.user.pk)
    except Notification.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
    n.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def notification_mark_read(request: Request, pk: int) -> Response:
    """POST /api/v1/notifications/<pk>/read/"""
    try:
        n = sel.get_notification(pk, request.user.pk)
    except Notification.DoesNotExist:
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    if n.read_at is None:
        n.read_at = timezone.now()
        n.save(update_fields=["read_at"])

    return Response(_serialize_notification(n))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def notification_mark_all_read(request: Request) -> Response:
    """POST /api/v1/notifications/read-all/

    Marks all unread inbox notifications for the current user as read.
    Returns ``{"marked": N}``.
    """
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)

    count = Notification.objects.filter(
        recipient=request.user,
        tenant=tenant,
        read_at__isnull=True,
    ).update(read_at=timezone.now())

    return Response({"marked": count})


# ---------------------------------------------------------------------------
# Preferences
# ---------------------------------------------------------------------------


def _CHANNELS() -> list[str]:
    return [c.value for c in NotificationChannel]


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def notification_preferences_list(request: Request) -> Response:
    """GET /api/v1/notifications/preferences/

    Returns all known notification types with per-channel opt-in state for
    the current user.  Uses registry for canonical type list; DB preferences
    override defaults.
    """
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)

    # Build {kind: {channel: enabled}} from DB
    db_prefs: dict[str, dict[str, bool]] = {}
    for pref in sel.list_preferences(request.user.pk, tenant.pk):
        db_prefs.setdefault(pref.kind, {})[pref.channel] = pref.enabled

    result = []
    for spec in get_all_notification_types():
        channels = {}
        for channel in _CHANNELS():
            if spec.is_critical:
                channels[channel] = True
            else:
                default = channel in spec.default_channels
                channels[channel] = db_prefs.get(spec.code, {}).get(channel, default)
        result.append(
            {
                "kind": spec.code,
                "label": spec.label,
                "is_critical": spec.is_critical,
                "channels": channels,
            }
        )
    return Response(result)


@api_view(["PUT"])
@permission_classes([IsAuthenticated])
def notification_preferences_detail(request: Request, kind: str) -> Response:
    """PUT /api/v1/notifications/preferences/<kind>/

    Body: ``{"channel": "email", "enabled": false}``
    """
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)

    channel = request.data.get("channel")
    enabled = request.data.get("enabled")

    if channel not in _CHANNELS():
        return Response(
            {"detail": f"Invalid channel. Choose from {_CHANNELS()}."},
            status=status.HTTP_400_BAD_REQUEST,
        )
    if not isinstance(enabled, bool):
        return Response({"detail": "enabled must be a boolean."}, status=status.HTTP_400_BAD_REQUEST)

    from simorgh.apps.notifications.registry import get_notification_type

    spec = get_notification_type(kind)
    if spec and spec.is_critical:
        return Response(
            {"detail": "Cannot opt out of critical notifications."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    org_node_id = getattr(getattr(request, "org_node", None), "pk", None) or 0
    pref = set_preference(
        tenant_id=tenant.pk,
        organization_node_id=org_node_id,
        user_id=request.user.pk,
        kind=kind,
        channel=channel,
        enabled=enabled,
    )
    return Response(
        {
            "kind": pref.kind,
            "channel": pref.channel,
            "enabled": pref.enabled,
        }
    )


# ---------------------------------------------------------------------------
# Quiet Hours
# ---------------------------------------------------------------------------


def _serialize_quiet_hours(qh: UserQuietHours | None) -> dict:
    if qh is None:
        return {"is_enabled": False, "start": None, "end": None, "timezone": "UTC"}
    return {
        "is_enabled": qh.is_enabled,
        "start": qh.start.strftime("%H:%M") if qh.start else None,
        "end": qh.end.strftime("%H:%M") if qh.end else None,
        "timezone": qh.timezone,
    }


@api_view(["GET", "PUT"])
@permission_classes([IsAuthenticated])
def notification_quiet_hours(request: Request) -> Response:
    """GET/PUT /api/v1/notifications/quiet-hours/"""
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        qh = sel.get_quiet_hours(request.user.pk, tenant.pk)
        return Response(_serialize_quiet_hours(qh))

    # PUT
    data = request.data
    is_enabled = data.get("is_enabled", False)
    tz = data.get("timezone", "UTC")

    if is_enabled:
        start_raw = data.get("start")
        end_raw = data.get("end")
        if not start_raw or not end_raw:
            return Response(
                {"detail": "start and end are required when is_enabled=true."},
                status=status.HTTP_400_BAD_REQUEST,
            )
    else:
        start_raw = data.get("start", "00:00")
        end_raw = data.get("end", "00:00")

    from django.core.exceptions import ValidationError as DjangoValidationError

    try:
        import datetime

        start_time = datetime.time.fromisoformat(start_raw)
        end_time = datetime.time.fromisoformat(end_raw)
    except (ValueError, TypeError):
        return Response(
            {"detail": "start/end must be HH:MM or HH:MM:SS format."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    org_node_id = getattr(getattr(request, "org_node", None), "pk", None) or 0
    qh, _ = UserQuietHours.objects.update_or_create(
        user=request.user,
        tenant=tenant,
        defaults={
            "is_enabled": is_enabled,
            "start": start_time,
            "end": end_time,
            "timezone": tz,
            "organization_node_id": org_node_id,
        },
    )
    return Response(_serialize_quiet_hours(qh))


# ---------------------------------------------------------------------------
# Web Push
# ---------------------------------------------------------------------------


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def notification_vapid_public_key(request: Request) -> Response:
    """GET /api/v1/notifications/push/vapid-public-key/

    Returns the VAPID public key so the browser can subscribe.
    """
    public_key = getattr(settings, "NOTIFICATIONS_VAPID_PUBLIC_KEY", "")
    return Response({"vapid_public_key": public_key})


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def notification_push_subscribe(request: Request) -> Response:
    """POST /api/v1/notifications/push/subscribe/

    Body: ``{endpoint, keys: {p256dh, auth}, device_name?}``
    """
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return Response({"detail": "Tenant not found."}, status=status.HTTP_404_NOT_FOUND)

    endpoint = request.data.get("endpoint")
    keys = request.data.get("keys") or {}
    p256dh = keys.get("p256dh")
    auth_key = keys.get("auth")
    device_name = request.data.get("device_name", "")

    if not endpoint or not p256dh or not auth_key:
        return Response(
            {"detail": "endpoint, keys.p256dh and keys.auth are required."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    org_node_id = getattr(getattr(request, "org_node", None), "pk", None) or 0
    sub, created = PushSubscription.objects.update_or_create(
        user=request.user,
        tenant=tenant,
        endpoint=endpoint,
        defaults={
            "p256dh_key": p256dh,
            "auth_key": auth_key,
            "device_name": device_name,
            "is_active": True,
            "organization_node_id": org_node_id,
        },
    )
    return Response(
        {"id": str(sub.pk), "device_name": sub.device_name, "created": created},
        status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
    )


@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def notification_push_unsubscribe(request: Request, pk: str) -> Response:
    """DELETE /api/v1/notifications/push/subscribe/<pk>/"""
    tenant = getattr(request, "tenant", None)
    deleted, _ = PushSubscription.objects.filter(
        public_id=pk,
        user=request.user,
        tenant=tenant,
    ).delete()
    if not deleted:
        return Response(status=status.HTTP_404_NOT_FOUND)
    return Response(status=status.HTTP_204_NO_CONTENT)
