"""
FastAPI chat REST API — conversation CRUD with RBAC.

Provides conversation management for the Phase 6 web chat feature.
All endpoints require portal authentication via X-Portal-User-Id headers
and enforce tenant membership (or platform_admin bypass).

Endpoints:
  GET    /api/portal/chat/conversations                   — list conversations
  POST   /api/portal/chat/conversations                   — create or get-or-create
  GET    /api/portal/chat/conversations/{id}/messages     — paginated history
  DELETE /api/portal/chat/conversations/{id}              — reset conversation

RBAC:
  - platform_admin: can access any tenant's conversations
  - customer_admin / customer_operator: must be a member of the target tenant
  - Other roles: 403

RLS:
  All DB queries set current_tenant_id context var before executing so
  PostgreSQL's FORCE ROW LEVEL SECURITY policy is applied automatically.
"""

from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from sqlalchemy import delete, select, text
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from shared.api.rbac import PortalCaller, get_portal_caller, require_tenant_member
from shared.db import get_session, engine
from shared.models.chat import WebConversation, WebConversationMessage
from shared.models.tenant import Agent
from shared.rls import configure_rls_hook, current_tenant_id

chat_router = APIRouter(prefix="/api/portal/chat", tags=["chat"])


# ---------------------------------------------------------------------------
# Pydantic schemas
# ---------------------------------------------------------------------------


class ConversationOut(BaseModel):
    id: str
    tenant_id: str
    agent_id: str
    agent_name: str | None = None
    user_id: str
    created_at: datetime
    updated_at: datetime
    last_message_preview: str | None = None


class ConversationCreate(BaseModel):
    tenant_id: uuid.UUID
    agent_id: uuid.UUID


class MessageOut(BaseModel):
    id: str
    role: str
    content: str
    created_at: datetime


class DeleteResult(BaseModel):
    deleted: bool
    conversation_id: str


# ---------------------------------------------------------------------------
# Helper: configure RLS and set context var
# ---------------------------------------------------------------------------


def _rls_set(engine_: Any, tenant_uuid: uuid.UUID) -> Any:
    """Configure RLS hook and set the tenant context variable."""
    configure_rls_hook(engine_)
    return current_tenant_id.set(tenant_uuid)


# ---------------------------------------------------------------------------
# GET /api/portal/chat/conversations
# ---------------------------------------------------------------------------


@chat_router.get("/conversations", response_model=list[ConversationOut])
async def list_conversations(
    tenant_id: uuid.UUID = Query(...),
    caller: PortalCaller = Depends(get_portal_caller),
    session: AsyncSession = Depends(get_session),
) -> list[ConversationOut]:
    """
    List conversations for the authenticated user within a tenant.

    Platform admins can see all conversations for the tenant.
    Other users see only their own conversations.
    """
    # RBAC — raises 403 if caller is not a member (platform_admin bypasses)
    await require_tenant_member(tenant_id=tenant_id, caller=caller, session=session)

    token = _rls_set(engine, tenant_id)
    try:
        stmt = (
            select(WebConversation, Agent.name.label("agent_name"))
            .join(Agent, WebConversation.agent_id == Agent.id, isouter=True)
            .where(WebConversation.tenant_id == tenant_id)
        )
        # Non-admins only see their own conversations
        if caller.role != "platform_admin":
            stmt = stmt.where(WebConversation.user_id == caller.user_id)

        stmt = stmt.order_by(WebConversation.updated_at.desc())
        result = await session.execute(stmt)
        rows = result.all()

        conversations: list[ConversationOut] = []
        for row in rows:
            conv = row[0]
            agent_name = row[1] if len(row) > 1 else None
            conversations.append(
                ConversationOut(
                    id=str(conv.id),
                    tenant_id=str(conv.tenant_id),
                    agent_id=str(conv.agent_id),
                    agent_name=agent_name,
                    user_id=str(conv.user_id),
                    created_at=conv.created_at,
                    updated_at=conv.updated_at,
                )
            )
        return conversations
    finally:
        current_tenant_id.reset(token)


# ---------------------------------------------------------------------------
# POST /api/portal/chat/conversations
# ---------------------------------------------------------------------------


@chat_router.post("/conversations", response_model=ConversationOut, status_code=status.HTTP_200_OK)
async def create_conversation(
    body: ConversationCreate,
    caller: PortalCaller = Depends(get_portal_caller),
    session: AsyncSession = Depends(get_session),
) -> ConversationOut:
    """
    Create or get an existing conversation for the caller + agent pair.

    Uses get-or-create semantics: if a conversation already exists for this
    (tenant_id, agent_id, user_id) triple, it is returned rather than creating
    a duplicate.
    """
    # RBAC
    await require_tenant_member(tenant_id=body.tenant_id, caller=caller, session=session)

    token = _rls_set(engine, body.tenant_id)
    try:
        # Check for existing conversation
        existing_stmt = select(WebConversation).where(
            WebConversation.tenant_id == body.tenant_id,
            WebConversation.agent_id == body.agent_id,
            WebConversation.user_id == caller.user_id,
        )
        existing_result = await session.execute(existing_stmt)
        existing = existing_result.scalar_one_or_none()

        if existing is not None:
            return ConversationOut(
                id=str(existing.id),
                tenant_id=str(existing.tenant_id),
                agent_id=str(existing.agent_id),
                user_id=str(existing.user_id),
                created_at=existing.created_at,
                updated_at=existing.updated_at,
            )

        # Create new conversation
        new_conv = WebConversation(
            id=uuid.uuid4(),
            tenant_id=body.tenant_id,
            agent_id=body.agent_id,
            user_id=caller.user_id,
        )
        session.add(new_conv)
        try:
            await session.flush()
            await session.commit()
            await session.refresh(new_conv)
        except IntegrityError:
            # Race condition: another request created it between our SELECT and INSERT
            await session.rollback()
            existing_result2 = await session.execute(existing_stmt)
            existing2 = existing_result2.scalar_one_or_none()
            if existing2 is None:
                raise HTTPException(
                    status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                    detail="Failed to create conversation",
                )
            return ConversationOut(
                id=str(existing2.id),
                tenant_id=str(existing2.tenant_id),
                agent_id=str(existing2.agent_id),
                user_id=str(existing2.user_id),
                created_at=existing2.created_at,
                updated_at=existing2.updated_at,
            )

        return ConversationOut(
            id=str(new_conv.id),
            tenant_id=str(new_conv.tenant_id),
            agent_id=str(new_conv.agent_id),
            user_id=str(new_conv.user_id),
            created_at=new_conv.created_at,
            updated_at=new_conv.updated_at,
        )
    finally:
        current_tenant_id.reset(token)


# ---------------------------------------------------------------------------
# GET /api/portal/chat/conversations/{id}/messages
# ---------------------------------------------------------------------------


@chat_router.get("/conversations/{conversation_id}/messages", response_model=list[MessageOut])
async def list_messages(
    conversation_id: uuid.UUID,
    limit: int = Query(default=50, ge=1, le=200),
    before: str | None = Query(default=None),
    caller: PortalCaller = Depends(get_portal_caller),
    session: AsyncSession = Depends(get_session),
) -> list[MessageOut]:
    """
    Return paginated message history for a conversation.

    Messages ordered by created_at ASC (oldest first).
    Cursor pagination via `before` parameter (message ID).

    Ownership enforced: caller must own the conversation OR be platform_admin.
    """
    # Set tenant context for RLS — use caller's tenant_id first to allow the lookup
    # For platform admins, temporarily set from the header (may be the selected tenant)
    initial_tenant_id = caller.tenant_id
    if initial_tenant_id:
        initial_token = _rls_set(engine, initial_tenant_id)
    else:
        initial_token = None

    try:
        conv_stmt = select(WebConversation).where(WebConversation.id == conversation_id)
        conv_result = await session.execute(conv_stmt)
        conversation = conv_result.scalar_one_or_none()
    finally:
        if initial_token is not None:
            current_tenant_id.reset(initial_token)

    if conversation is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")

    # Ownership check: caller owns the conversation or is platform_admin
    if caller.role != "platform_admin" and conversation.user_id != caller.user_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have access to this conversation",
        )

    token = _rls_set(engine, conversation.tenant_id)
    try:
        msg_stmt = (
            select(WebConversationMessage)
            .where(WebConversationMessage.conversation_id == conversation_id)
            .order_by(WebConversationMessage.created_at.asc())
            .limit(limit)
        )

        if before:
            try:
                before_uuid = uuid.UUID(before)
                # Get the cursor message's created_at
                cursor_stmt = select(WebConversationMessage.created_at).where(
                    WebConversationMessage.id == before_uuid
                )
                cursor_result = await session.execute(cursor_stmt)
                cursor_ts = cursor_result.scalar_one_or_none()
                if cursor_ts is not None:
                    msg_stmt = msg_stmt.where(WebConversationMessage.created_at < cursor_ts)
            except (ValueError, AttributeError):
                pass  # Invalid cursor — ignore and return from start

        msg_result = await session.execute(msg_stmt)
        messages = msg_result.scalars().all()

        return [
            MessageOut(
                id=str(m.id),
                role=m.role,
                content=m.content,
                created_at=m.created_at,
            )
            for m in messages
        ]
    finally:
        current_tenant_id.reset(token)


# ---------------------------------------------------------------------------
# DELETE /api/portal/chat/conversations/{id}
# ---------------------------------------------------------------------------


@chat_router.delete("/conversations/{conversation_id}", response_model=DeleteResult)
async def reset_conversation(
    conversation_id: uuid.UUID,
    caller: PortalCaller = Depends(get_portal_caller),
    session: AsyncSession = Depends(get_session),
) -> DeleteResult:
    """
    Reset a conversation by deleting all messages.

    The conversation row is kept but all messages are deleted.
    Updates updated_at on the conversation.

    Ownership enforced: caller must own the conversation OR be platform_admin.
    """
    # Fetch conversation
    conv_stmt = select(WebConversation).where(WebConversation.id == conversation_id)
    conv_result = await session.execute(conv_stmt)
    conversation = conv_result.scalar_one_or_none()

    if conversation is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")

    # Ownership check
    if caller.role != "platform_admin" and conversation.user_id != caller.user_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not have access to this conversation",
        )

    token = _rls_set(engine, conversation.tenant_id)
    try:
        # Delete all messages for this conversation
        delete_stmt = delete(WebConversationMessage).where(
            WebConversationMessage.conversation_id == conversation_id
        )
        await session.execute(delete_stmt)

        # Update conversation timestamp
        await session.execute(
            text("UPDATE web_conversations SET updated_at = NOW() WHERE id = :conv_id"),
            {"conv_id": str(conversation_id)},
        )

        await session.commit()

        return DeleteResult(deleted=True, conversation_id=str(conversation_id))
    finally:
        current_tenant_id.reset(token)
