"""Notification center API — list, count, mark-read."""
import uuid
from datetime import datetime
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, update, or_, and_

from app.database import get_db
from app.models.audit_log import AuditLog
from app.models.user import User
from app.utils.auth import get_current_user

router = APIRouter(prefix="/notifications", tags=["notifications"])


class NotificationOut(BaseModel):
    id: str
    action: str
    entity_type: str | None = None
    entity_id: str | None = None
    details: dict | None = None
    timestamp: datetime
    read_at: datetime | None = None

    model_config = {"from_attributes": True}


class NotificationListResponse(BaseModel):
    items: list[NotificationOut]
    unread_count: int
    total: int


class UnreadCountResponse(BaseModel):
    unread_count: int


class MarkReadRequest(BaseModel):
    notification_ids: list[str] | None = None


def _visibility_filter(user: User):
    """Rows visible to this user: targeted at them, or broadcast (null) if admin/PM."""
    targeted = AuditLog.target_user_id == user.id
    if user.role.value in ("admin", "project_manager"):
        broadcast = AuditLog.target_user_id.is_(None)
        return and_(AuditLog.notification == True, or_(targeted, broadcast))  # noqa: E712
    return and_(AuditLog.notification == True, targeted)  # noqa: E712


@router.get("", response_model=NotificationListResponse)
async def list_notifications(
    limit: int = Query(20, ge=1, le=100),
    offset: int = Query(0, ge=0),
    unread_only: bool = Query(False),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    vis = _visibility_filter(user)

    # Total count
    total_q = select(func.count(AuditLog.id)).where(vis)
    if unread_only:
        total_q = total_q.where(AuditLog.read_at.is_(None))
    total = (await db.execute(total_q)).scalar() or 0

    # Unread count (always)
    unread_q = select(func.count(AuditLog.id)).where(vis, AuditLog.read_at.is_(None))
    unread_count = (await db.execute(unread_q)).scalar() or 0

    # Items
    items_q = (
        select(AuditLog)
        .where(vis)
        .order_by(AuditLog.timestamp.desc())
        .offset(offset)
        .limit(limit)
    )
    if unread_only:
        items_q = items_q.where(AuditLog.read_at.is_(None))

    rows = (await db.execute(items_q)).scalars().all()
    items = [
        NotificationOut(
            id=str(r.id),
            action=r.action,
            entity_type=r.entity_type,
            entity_id=r.entity_id,
            details=r.details,
            timestamp=r.timestamp,
            read_at=r.read_at,
        )
        for r in rows
    ]

    return NotificationListResponse(items=items, unread_count=unread_count, total=total)


@router.get("/unread-count", response_model=UnreadCountResponse)
async def unread_count(
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    vis = _visibility_filter(user)
    q = select(func.count(AuditLog.id)).where(vis, AuditLog.read_at.is_(None))
    count = (await db.execute(q)).scalar() or 0
    return UnreadCountResponse(unread_count=count)


@router.post("/mark-read")
async def mark_read(
    body: MarkReadRequest,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Mark notifications as read. If notification_ids is null, mark all as read."""
    vis = _visibility_filter(user)
    now = datetime.utcnow()

    if body.notification_ids is None:
        # Mark all unread
        stmt = (
            update(AuditLog)
            .where(vis, AuditLog.read_at.is_(None))
            .values(read_at=now)
        )
    else:
        ids = [uuid.UUID(nid) for nid in body.notification_ids]
        stmt = (
            update(AuditLog)
            .where(vis, AuditLog.id.in_(ids), AuditLog.read_at.is_(None))
            .values(read_at=now)
        )

    await db.execute(stmt)
    await db.commit()
    return {"ok": True}


@router.post("/{notification_id}/mark-read")
async def mark_one_read(
    notification_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    vis = _visibility_filter(user)
    now = datetime.utcnow()
    result = await db.execute(
        update(AuditLog)
        .where(vis, AuditLog.id == notification_id, AuditLog.read_at.is_(None))
        .values(read_at=now)
    )
    await db.commit()
    return {"ok": True}


# ── Notification Config Endpoints ────────────────────────────────────────────
from app.domains.notifications.schemas import NotificationConfigOut, NotificationConfigUpdate
from app.domains.notifications.service import (
    get_notification_configs, upsert_notification_config, reset_notification_configs
)


@router.get("/config", response_model=list[NotificationConfigOut])
async def get_my_notification_config(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    return await get_notification_configs(db, current_user.id)


@router.put("/config/{event_type}/{channel}", response_model=NotificationConfigOut)
async def update_my_notification_config(
    event_type: str,
    channel: str,
    body: NotificationConfigUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    if channel not in ("in_app", "email"):
        raise HTTPException(status_code=400, detail="channel must be 'in_app' or 'email'")
    return await upsert_notification_config(db, current_user.id, event_type, channel, body.enabled)


@router.post("/config/reset", response_model=list[NotificationConfigOut])
async def reset_my_notification_config(
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    return await reset_notification_configs(db, current_user.id)
