import uuid
from datetime import datetime, timezone
from fastapi import APIRouter, Request, Query, status, HTTPException, Depends
from app.models.session import (
    SessionInitRequest,
    SessionInitResponse,
    SessionHistoryResponse,
    MessageHistoryItem
)
from app.repositories.tenant_repository import get_tenant_by_site_id
from app.repositories.session_repository import (
    create_session,
    get_session_by_id,
    update_session_status,
    update_session_connection_status
)
from app.repositories.conversation_repository import (
    get_conversation_by_session_id,
    get_active_conversation_by_session_id,
    get_conversation_by_id
)
from app.repositories.message_repository import get_messages_by_conversation_id
from app.dependencies.auth import get_current_agent_optional
from app.database import get_database
from app.services.presence import (
    active_sessions,
    add_active_session,
    broadcast_to_agents,
    notify_visitor_socket,
    cancel_grace_period_timer
)
from app.utils.origin_validator import is_origin_allowed
from app.utils.ip_extractor import extract_client_ip

router = APIRouter(prefix="/api/v1/session", tags=["session"])
conversation_router = APIRouter(prefix="/api/v1/conversation", tags=["conversation"])

@router.post("/init", response_model=SessionInitResponse, status_code=status.HTTP_201_CREATED)
async def init_session(payload: SessionInitRequest, request: Request):
    db = get_database()
    
    # Step 1: Find tenant by siteId
    tenant = await get_tenant_by_site_id(db, payload.siteId)
    if not tenant:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Tenant not found for siteId '{payload.siteId}'"
        )
    
    # Step 2: Extract HTTP Origin header from request
    origin = request.headers.get("origin") or request.headers.get("referer")
    
    # Step 3: Validate request Origin against registered Tenant domain
    if not is_origin_allowed(origin, tenant["domain"]):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=f"Origin '{origin}' is not authorized for siteId '{payload.siteId}'"
        )
    
    # Step 4: Extract Client IP address and optional Visitor User data
    client_ip = extract_client_ip(request)
    user_dict = payload.user.model_dump() if payload.user else None

    # Step 5: Create Session in MongoDB
    session_doc = await create_session(
        db=db,
        site_id=payload.siteId,
        user_data=user_dict,
        ip_address=client_ip
    )
    return session_doc

@router.get("/{sessionId}/history", response_model=SessionHistoryResponse, status_code=status.HTTP_200_OK)
async def get_session_history(
    sessionId: str,
    siteId: str | None = Query(default=None, description="Optional siteId for visitor access validation"),
    authenticated_agent: dict | None = Depends(get_current_agent_optional)
):
    """
    Read-only endpoint to retrieve persisted session metadata and conversation message history from MongoDB.
    
    Security:
    - Authenticated agents (with Bearer JWT) can access any session.
    - Unauthenticated callers (visitor widget) must supply a matching siteId parameter.
    """
    db = get_database()
    
    # 1. Resolve session directly from sessions collection in MongoDB
    session = await get_session_by_id(db, sessionId)
    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Session '{sessionId}' not found"
        )
        
    # Security / Access Control Check
    if not authenticated_agent:
        if not siteId or siteId != session.get("siteId"):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Access denied for session history"
            )

    # 2. Resolve conversation associated with session (read-only)
    conv = await get_conversation_by_session_id(db, sessionId)
    
    # 3. Fetch persisted messages in chronological order
    messages_list = []
    if conv:
        raw_messages = await get_messages_by_conversation_id(db, conv["conversationId"])
        for m in raw_messages:
            created_at_val = m.get("createdAt")
            created_at_str = created_at_val.isoformat() if hasattr(created_at_val, "isoformat") else str(created_at_val)
            messages_list.append(MessageHistoryItem(
                messageId=m["messageId"],
                role=m["role"],
                content=m["content"],
                createdAt=created_at_str,
                agentId=m.get("agentId")
            ))

    assigned_at_val = session.get("assignedAt")
    assigned_at_str = assigned_at_val.isoformat() if hasattr(assigned_at_val, "isoformat") else str(assigned_at_val) if assigned_at_val else None

    ended_by = session.get("endedBy") or (conv.get("endedBy") if conv else None)

    created_at_val = session.get("createdAt")
    connected_at_str = active_sessions.get(sessionId, {}).get("connectedAt") or (created_at_val.isoformat() if hasattr(created_at_val, "isoformat") else str(created_at_val) if created_at_val else None)

    return SessionHistoryResponse(
        sessionId=session["sessionId"],
        siteId=session["siteId"],
        status=session.get("status", "RINGING"),
        mode=session.get("mode", "rule"),
        assignedAgentId=session.get("assignedAgentId"),
        assignedAt=assigned_at_str,
        endedBy=ended_by,
        ipAddress=session.get("ipAddress"),
        country=session.get("country", "Unknown"),
        connectedAt=connected_at_str,
        conversationId=conv["conversationId"] if conv else None,
        messages=messages_list
    )

@router.post("/{sessionId}/new-conversation", status_code=status.HTTP_200_OK)
async def create_new_conversation_endpoint(sessionId: str):
    db = get_database()
    session = await get_session_by_id(db, sessionId)
    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Session '{sessionId}' not found"
        )
    
    site_id = session.get("siteId", "")
    
    # Validate that current conversation is ENDED
    active_conv = await get_active_conversation_by_session_id(db, sessionId)
    if active_conv and active_conv.get("status") not in ("ended", "ENDED"):
        if session.get("status") != "ENDED":
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot start a new conversation while an active conversation exists for this session"
            )

    # Create brand-new Conversation document
    conv_id = f"conv_{uuid.uuid4().hex[:12]}"
    now = datetime.now(timezone.utc)
    conv_doc = {
        "conversationId": conv_id,
        "sessionId": sessionId,
        "siteId": site_id,
        "status": "open",
        "createdAt": now,
        "updatedAt": now
    }
    await db["conversations"].insert_one(conv_doc)

    # Reset Session status to RINGING and unassign agent
    await update_session_status(db, sessionId, "RINGING")
    await update_session_connection_status(db, sessionId, "online", disconnected_at=None)
    await db["sessions"].update_one(
        {"sessionId": sessionId},
        {
            "$set": {"assignedAgentId": None, "assignedAt": None},
            "$unset": {"endedBy": ""}
        }
    )

    cancel_grace_period_timer(sessionId)

    user_data = None
    if session.get("userId") or session.get("userName") or session.get("userEmail"):
        user_data = {
            "id": session.get("userId"),
            "name": session.get("userName"),
            "email": session.get("userEmail")
        }

    active_info = add_active_session(
        session_id=sessionId,
        site_id=site_id,
        websocket=active_sessions.get(sessionId, {}).get("websocket"),
        user_data=user_data,
        ip_address=session.get("ipAddress"),
        country=session.get("country", "Unknown"),
        status_val="RINGING",
        assigned_agent_id=None,
        assigned_at=None,
        connection_status="online",
        disconnected_at=None
    )

    # Broadcast ring event to all online agents
    await broadcast_to_agents({
        "type": "visitor_connected",
        "sessionId": sessionId,
        "conversationId": conv_id,
        "siteId": site_id,
        "status": "RINGING",
        "connectionStatus": "online",
        "assignedAgentId": None,
        "user": user_data,
        "ipAddress": session.get("ipAddress"),
        "country": session.get("country", "Unknown"),
        "connectedAt": active_info["connectedAt"],
        "isNewConversation": True
    })

    # Notify visitor socket
    await notify_visitor_socket(sessionId, {
        "type": "status_changed",
        "status": "RINGING",
        "conversationId": conv_id,
        "message": "Waiting for an agent to connect..."
    })

    return {
        "sessionId": sessionId,
        "conversationId": conv_id,
        "status": "RINGING"
    }

@conversation_router.get("/{conversationId}", status_code=status.HTTP_200_OK)
async def get_conversation_by_id_endpoint(conversationId: str):
    db = get_database()
    conv = await get_conversation_by_id(db, conversationId)
    if not conv:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Conversation '{conversationId}' not found"
        )

    raw_messages = await get_messages_by_conversation_id(db, conversationId)
    messages_list = []
    for m in raw_messages:
        created_at_val = m.get("createdAt")
        created_at_str = created_at_val.isoformat() if hasattr(created_at_val, "isoformat") else str(created_at_val)
        messages_list.append({
            "messageId": m["messageId"],
            "role": m["role"],
            "content": m["content"],
            "createdAt": created_at_str,
            "agentId": m.get("agentId")
        })

    return {
        "conversationId": conv["conversationId"],
        "sessionId": conv["sessionId"],
        "siteId": conv["siteId"],
        "status": conv.get("status", "open"),
        "endedBy": conv.get("endedBy"),
        "messages": messages_list
    }
