"""
Chat router — /api/chat
"""
from datetime import datetime, timezone
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session

from database import get_db
from routers.deps import get_current_user
from models.user import User
from models.chat import Conversation, ConversationParticipant, ChatMessage
from schemas.user import UserOut
from schemas.chat import (
    ConversationCreate,
    ConversationOut,
    MessageCreate,
    MessageOut,
    ParticipantOut,
)
from services.sse_bus import broadcast_sync

router = APIRouter()


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def _build_participant_out(p: ConversationParticipant) -> ParticipantOut:
    return ParticipantOut(
        user_id=p.user_id,
        username=p.user.username,
        joined_at=p.joined_at,
        last_read_at=p.last_read_at,
    )


def _build_message_out(msg: ChatMessage) -> MessageOut:
    return MessageOut(
        id=msg.id,
        conversation_id=msg.conversation_id,
        sender_id=msg.sender_id,
        sender_name=msg.sender.username,
        body=msg.body,
        sent_at=msg.sent_at,
        is_deleted=msg.deleted_at is not None,
    )


def _unread_count(db: Session, conv_id: int, user: User) -> int:
    """
    Count messages in a conversation that are 'unread' for this user:
      - not soft-deleted
      - not sent by this user (your own messages don't count)
      - sent after user's last_read_at (or ALL messages if last_read_at is None)
    """
    participant = (
        db.query(ConversationParticipant)
        .filter(
            ConversationParticipant.conversation_id == conv_id,
            ConversationParticipant.user_id == user.id,
        )
        .first()
    )
    if not participant:
        return 0

    q = (
        db.query(ChatMessage)
        .filter(
            ChatMessage.conversation_id == conv_id,
            ChatMessage.deleted_at == None,  # noqa: E711
            ChatMessage.sender_id != user.id,
        )
    )
    if participant.last_read_at is not None:
        q = q.filter(ChatMessage.sent_at > participant.last_read_at)
    return q.count()


def _last_message(db: Session, conv_id: int) -> Optional[ChatMessage]:
    return (
        db.query(ChatMessage)
        .filter(
            ChatMessage.conversation_id == conv_id,
            ChatMessage.deleted_at == None,  # noqa: E711
        )
        .order_by(ChatMessage.sent_at.desc())
        .first()
    )


def _get_participant_ids(conv: Conversation) -> List[int]:
    return [p.user_id for p in conv.participants]


def _build_conversation_out(db: Session, conv: Conversation, user: User) -> ConversationOut:
    last_msg = _last_message(db, conv.id)
    return ConversationOut(
        id=conv.id,
        type=conv.type,
        name=conv.name,
        is_system=conv.is_system,
        created_at=conv.created_at,
        participants=[_build_participant_out(p) for p in conv.participants],
        last_message=_build_message_out(last_msg) if last_msg else None,
        unread_count=_unread_count(db, conv.id, user),
    )


def _require_participant(db: Session, conv_id: int, user: User) -> Conversation:
    """Return the conversation if user is a participant, else 403/404."""
    conv = db.query(Conversation).filter(Conversation.id == conv_id).first()
    if not conv:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")
    is_participant = any(p.user_id == user.id for p in conv.participants)
    if not is_participant:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not a participant")
    return conv


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------

@router.get("/conversations", response_model=List[ConversationOut])
def list_conversations(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """List all conversations the current user is a participant of, ordered by last message time desc."""
    # Fetch all conversation_ids for this user
    participant_rows = (
        db.query(ConversationParticipant)
        .filter(ConversationParticipant.user_id == user.id)
        .all()
    )
    conv_ids = [p.conversation_id for p in participant_rows]
    if not conv_ids:
        return []

    conversations = (
        db.query(Conversation)
        .filter(Conversation.id.in_(conv_ids))
        .all()
    )

    # Sort by last message sent_at desc (conversations with no messages go last)
    # SQLite returns naive datetimes even for timezone=True columns, so keep everything naive
    def _sort_key(c: Conversation):
        last = _last_message(db, c.id)
        if not last:
            return datetime.min
        dt = last.sent_at
        return dt.replace(tzinfo=None) if dt.tzinfo else dt

    conversations.sort(key=_sort_key, reverse=True)

    return [_build_conversation_out(db, c, user) for c in conversations]


@router.post("/conversations", response_model=ConversationOut, status_code=status.HTTP_201_CREATED)
def create_conversation(
    payload: ConversationCreate,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """
    Create a direct or group conversation.
    For direct: returns existing conversation if one already exists between the two users.
    Creator is automatically added as a participant.
    """
    # Normalise participant list — always include creator
    participant_ids = list(set(payload.participant_ids + [user.id]))

    if payload.type == "direct":
        if len(participant_ids) != 2:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Direct conversations must have exactly 2 participants",
            )
        other_id = next(pid for pid in participant_ids if pid != user.id)

        # Check for an existing non-system direct conversation between these two users
        existing_parts_me = (
            db.query(ConversationParticipant)
            .filter(ConversationParticipant.user_id == user.id)
            .all()
        )
        my_conv_ids = {p.conversation_id for p in existing_parts_me}

        existing_parts_other = (
            db.query(ConversationParticipant)
            .filter(
                ConversationParticipant.user_id == other_id,
                ConversationParticipant.conversation_id.in_(my_conv_ids),
            )
            .all()
        )
        shared_conv_ids = {p.conversation_id for p in existing_parts_other}

        for conv_id in shared_conv_ids:
            conv = db.query(Conversation).filter(
                Conversation.id == conv_id,
                Conversation.type == "direct",
                Conversation.is_system == False,  # noqa: E712
            ).first()
            if conv:
                return _build_conversation_out(db, conv, user)

    elif payload.type == "group":
        if not payload.name:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Group conversations require a name",
            )
    else:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="type must be 'direct' or 'group'",
        )

    # Create the conversation
    conv = Conversation(
        type=payload.type,
        name=payload.name if payload.type == "group" else None,
        is_system=False,
        created_by=user.id,
    )
    db.add(conv)
    db.flush()

    now = _utcnow()
    for uid in participant_ids:
        db.add(ConversationParticipant(
            conversation_id=conv.id,
            user_id=uid,
            joined_at=now,
        ))

    db.commit()
    db.refresh(conv)
    return _build_conversation_out(db, conv, user)


@router.get("/conversations/{conv_id}", response_model=ConversationOut)
def get_conversation(
    conv_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Get single conversation details with participants."""
    conv = _require_participant(db, conv_id, user)
    return _build_conversation_out(db, conv, user)


@router.get("/conversations/{conv_id}/messages", response_model=List[MessageOut])
def list_messages(
    conv_id: int,
    offset: int = Query(default=0, ge=0),
    limit: int = Query(default=50, ge=1, le=200),
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Paginated messages for a conversation, newest first. User must be a participant."""
    _require_participant(db, conv_id, user)

    messages = (
        db.query(ChatMessage)
        .filter(ChatMessage.conversation_id == conv_id)
        .order_by(ChatMessage.sent_at.desc())
        .offset(offset)
        .limit(limit)
        .all()
    )
    return [_build_message_out(m) for m in messages]


@router.post("/conversations/{conv_id}/messages", response_model=MessageOut, status_code=status.HTTP_201_CREATED)
def send_message(
    conv_id: int,
    payload: MessageCreate,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Send a message to a conversation. Broadcasts chat_message SSE event to all participants."""
    conv = _require_participant(db, conv_id, user)

    if not payload.body.strip():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Message body cannot be empty",
        )

    msg = ChatMessage(
        conversation_id=conv_id,
        sender_id=user.id,
        body=payload.body,
        sent_at=_utcnow(),
    )
    db.add(msg)
    db.commit()
    db.refresh(msg)

    participant_ids = _get_participant_ids(conv)
    # Ensure UTC Z suffix so JS Date() parses correctly regardless of tzinfo
    sent_at_str = msg.sent_at.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
    broadcast_sync(
        "chat_message",
        {
            "conversation_id": conv_id,
            "message_id": msg.id,
            "sender_id": user.id,
            "sender_name": user.username,
            "body": msg.body,
            "sent_at": sent_at_str,
        },
        user_ids=participant_ids,
    )

    return _build_message_out(msg)


@router.post("/conversations/{conv_id}/read", status_code=status.HTTP_204_NO_CONTENT)
def mark_read(
    conv_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """Update the current user's last_read_at to now. Broadcasts chat_read SSE event."""
    conv = _require_participant(db, conv_id, user)

    participant = (
        db.query(ConversationParticipant)
        .filter(
            ConversationParticipant.conversation_id == conv_id,
            ConversationParticipant.user_id == user.id,
        )
        .first()
    )
    now = _utcnow()
    participant.last_read_at = now
    db.commit()

    participant_ids = _get_participant_ids(conv)
    broadcast_sync(
        "chat_read",
        {
            "conversation_id": conv_id,
            "user_id": user.id,
            "read_at": now.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z',
        },
        user_ids=participant_ids,
    )


@router.post("/conversations/{conv_id}/leave", status_code=status.HTTP_204_NO_CONTENT)
def leave_conversation(
    conv_id: int,
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """
    Remove current user from the conversation.
    Not allowed on system conversations or direct conversations.
    """
    conv = _require_participant(db, conv_id, user)

    if conv.is_system:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Cannot leave the system group conversation",
        )
    if conv.type == "direct":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot leave a direct conversation — just stop using it",
        )

    participant = (
        db.query(ConversationParticipant)
        .filter(
            ConversationParticipant.conversation_id == conv_id,
            ConversationParticipant.user_id == user.id,
        )
        .first()
    )
    db.delete(participant)
    db.commit()


@router.get("/users", response_model=List[UserOut])
def list_chat_users(
    db: Session = Depends(get_db),
    user: User = Depends(get_current_user),
):
    """All active users — accessible to any authenticated staff member, for starting chats."""
    return db.query(User).filter(User.is_active == True).order_by(User.username).all()  # noqa: E712
