"""PostgreSQL storage operations for notifications (asyncpg, no ORM)."""

from __future__ import annotations

import uuid
from datetime import datetime, timezone

from notifications.config import get_pool
from notifications.models import (
    CreateNotification,
    DigestFrequency,
    Notification,
    NotificationPreferences,
    NotificationType,
)


async def create_tables() -> None:
    """Create the notifications and notification_preferences tables if they do not exist."""
    pool = get_pool()
    async with pool.acquire() as conn:
        await conn.execute("""
            CREATE TABLE IF NOT EXISTS notifications (
                id UUID PRIMARY KEY,
                user_id TEXT NOT NULL,
                type TEXT NOT NULL DEFAULT 'info',
                title TEXT NOT NULL,
                message TEXT NOT NULL,
                action_url TEXT,
                read BOOLEAN NOT NULL DEFAULT FALSE,
                created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
            )
        """)
        await conn.execute("""
            CREATE INDEX IF NOT EXISTS idx_notifications_user_id
            ON notifications (user_id, created_at DESC)
        """)
        await conn.execute("""
            CREATE INDEX IF NOT EXISTS idx_notifications_user_unread
            ON notifications (user_id) WHERE read = FALSE
        """)
        await conn.execute("""
            CREATE TABLE IF NOT EXISTS notification_preferences (
                user_id TEXT PRIMARY KEY,
                email_enabled BOOLEAN NOT NULL DEFAULT TRUE,
                in_app_enabled BOOLEAN NOT NULL DEFAULT TRUE,
                digest_frequency TEXT NOT NULL DEFAULT 'none'
            )
        """)


def _row_to_notification(row: dict) -> Notification:
    """Convert an asyncpg Record to a Notification model."""
    return Notification(
        id=row["id"],
        user_id=row["user_id"],
        type=NotificationType(row["type"]),
        title=row["title"],
        message=row["message"],
        action_url=row["action_url"],
        read=row["read"],
        created_at=row["created_at"],
    )


async def insert(notification: CreateNotification) -> Notification:
    """Insert a single notification and return it."""
    pool = get_pool()
    notification_id = uuid.uuid4()
    now = datetime.now(timezone.utc)

    async with pool.acquire() as conn:
        await conn.execute(
            """
            INSERT INTO notifications (id, user_id, type, title, message, action_url, read, created_at)
            VALUES ($1, $2, $3, $4, $5, $6, FALSE, $7)
            """,
            notification_id,
            notification.user_id,
            notification.type.value,
            notification.title,
            notification.message,
            notification.action_url,
            now,
        )

    return Notification(
        id=notification_id,
        user_id=notification.user_id,
        type=notification.type,
        title=notification.title,
        message=notification.message,
        action_url=notification.action_url,
        read=False,
        created_at=now,
    )


async def insert_bulk(notifications: list[CreateNotification]) -> list[Notification]:
    """Insert multiple notifications in a single transaction."""
    pool = get_pool()
    now = datetime.now(timezone.utc)
    results: list[Notification] = []

    async with pool.acquire() as conn:
        async with conn.transaction():
            stmt = await conn.prepare(
                """
                INSERT INTO notifications (id, user_id, type, title, message, action_url, read, created_at)
                VALUES ($1, $2, $3, $4, $5, $6, FALSE, $7)
                """
            )
            for n in notifications:
                nid = uuid.uuid4()
                await stmt.fetch(
                    nid,
                    n.user_id,
                    n.type.value,
                    n.title,
                    n.message,
                    n.action_url,
                    now,
                )
                results.append(
                    Notification(
                        id=nid,
                        user_id=n.user_id,
                        type=n.type,
                        title=n.title,
                        message=n.message,
                        action_url=n.action_url,
                        read=False,
                        created_at=now,
                    )
                )

    return results


async def get_by_user(
    user_id: str,
    limit: int = 50,
    offset: int = 0,
    unread_only: bool = False,
) -> list[Notification]:
    """Fetch notifications for a user, newest first."""
    pool = get_pool()
    async with pool.acquire() as conn:
        if unread_only:
            rows = await conn.fetch(
                """
                SELECT id, user_id, type, title, message, action_url, read, created_at
                FROM notifications
                WHERE user_id = $1 AND read = FALSE
                ORDER BY created_at DESC
                LIMIT $2 OFFSET $3
                """,
                user_id,
                limit,
                offset,
            )
        else:
            rows = await conn.fetch(
                """
                SELECT id, user_id, type, title, message, action_url, read, created_at
                FROM notifications
                WHERE user_id = $1
                ORDER BY created_at DESC
                LIMIT $2 OFFSET $3
                """,
                user_id,
                limit,
                offset,
            )

    return [_row_to_notification(dict(row)) for row in rows]


async def get_total_count(user_id: str, unread_only: bool = False) -> int:
    """Get total notification count for a user."""
    pool = get_pool()
    async with pool.acquire() as conn:
        if unread_only:
            row = await conn.fetchval(
                "SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND read = FALSE",
                user_id,
            )
        else:
            row = await conn.fetchval(
                "SELECT COUNT(*) FROM notifications WHERE user_id = $1",
                user_id,
            )
    return row or 0


async def mark_read(notification_id: uuid.UUID) -> bool:
    """Mark a single notification as read. Returns True if the notification existed."""
    pool = get_pool()
    async with pool.acquire() as conn:
        result = await conn.execute(
            "UPDATE notifications SET read = TRUE WHERE id = $1",
            notification_id,
        )
    return result == "UPDATE 1"


async def mark_all_read(user_id: str) -> int:
    """Mark all unread notifications for a user as read. Returns count of updated rows."""
    pool = get_pool()
    async with pool.acquire() as conn:
        result = await conn.execute(
            "UPDATE notifications SET read = TRUE WHERE user_id = $1 AND read = FALSE",
            user_id,
        )
    # result looks like "UPDATE N"
    return int(result.split()[-1])


async def get_unread_count(user_id: str) -> int:
    """Get the number of unread notifications for a user."""
    pool = get_pool()
    async with pool.acquire() as conn:
        count = await conn.fetchval(
            "SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND read = FALSE",
            user_id,
        )
    return count or 0


async def delete_old(days: int) -> int:
    """Delete notifications older than the specified number of days. Returns count deleted."""
    pool = get_pool()
    async with pool.acquire() as conn:
        result = await conn.execute(
            """
            DELETE FROM notifications
            WHERE created_at < NOW() - MAKE_INTERVAL(days => $1)
            """,
            days,
        )
    return int(result.split()[-1])


async def get_preferences(user_id: str) -> NotificationPreferences:
    """Get notification preferences for a user. Returns defaults if no row exists."""
    pool = get_pool()
    async with pool.acquire() as conn:
        row = await conn.fetchrow(
            """
            SELECT user_id, email_enabled, in_app_enabled, digest_frequency
            FROM notification_preferences
            WHERE user_id = $1
            """,
            user_id,
        )

    if row is None:
        return NotificationPreferences(user_id=user_id)

    return NotificationPreferences(
        user_id=row["user_id"],
        email_enabled=row["email_enabled"],
        in_app_enabled=row["in_app_enabled"],
        digest_frequency=DigestFrequency(row["digest_frequency"]),
    )


async def set_preferences(
    user_id: str, prefs: NotificationPreferences
) -> NotificationPreferences:
    """Upsert notification preferences for a user."""
    pool = get_pool()
    async with pool.acquire() as conn:
        await conn.execute(
            """
            INSERT INTO notification_preferences (user_id, email_enabled, in_app_enabled, digest_frequency)
            VALUES ($1, $2, $3, $4)
            ON CONFLICT (user_id)
            DO UPDATE SET
                email_enabled = EXCLUDED.email_enabled,
                in_app_enabled = EXCLUDED.in_app_enabled,
                digest_frequency = EXCLUDED.digest_frequency
            """,
            user_id,
            prefs.email_enabled,
            prefs.in_app_enabled,
            prefs.digest_frequency.value,
        )

    return prefs
