from fastapi import APIRouter, status, HTTPException, Depends
from pydantic import BaseModel, Field
from app.repositories.session_repository import get_session_by_id, claim_session_atomically
from app.repositories.conversation_repository import get_or_create_conversation
from app.repositories.message_repository import create_message
from app.database import get_database
from app.services.presence import (
    active_sessions,
    broadcast_to_agents,
    notify_visitor_socket,
    update_active_session_status,
    remove_active_session,
    cancel_grace_period_timer,
    set_agent_status
)
from app.repositories.session_repository import update_session_status
from app.dependencies.auth import get_current_agent_optional

router = APIRouter(prefix="/api/v1/handoff", tags=["handoff"])

class HandoffActionRequest(BaseModel):
    sessionId: str = Field(..., min_length=1)
    agentId: str | None = None
    endedBy: str | None = None

class HandoffActionResponse(BaseModel):
    sessionId: str
    status: str
    assignedAgentId: str | None = None

class AgentMessageRequest(BaseModel):
    sessionId: str = Field(..., min_length=1)
    message: str = Field(..., min_length=1)
    agentId: str | None = None

@router.post("/accept", response_model=HandoffActionResponse, status_code=status.HTTP_200_OK)
async def accept_visitor(
    payload: HandoffActionRequest,
    authenticated_agent: dict | None = Depends(get_current_agent_optional)
):
    db = get_database()
    
    # 1. Resolve claiming agent ID
    claiming_agent_id = payload.agentId
    if authenticated_agent and authenticated_agent.get("agentId"):
        claiming_agent_id = authenticated_agent["agentId"]

    if not claiming_agent_id:
        claiming_agent_id = "agent_1"

    # 2. Check if session exists in DB
    session = await get_session_by_id(db, payload.sessionId)
    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Session not found for sessionId '{payload.sessionId}'"
        )
        
    # 3. Attempt atomic claim in MongoDB
    claimed_doc = await claim_session_atomically(db, payload.sessionId, claiming_agent_id)
    if not claimed_doc:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Chat session already claimed by another agent"
        )

    # 4. Update in-memory active session status, assignment, and agent presence
    update_active_session_status(payload.sessionId, "IN_CHAT")
    set_agent_status(claiming_agent_id, "BUSY")
    if payload.sessionId in active_sessions:
        active_sessions[payload.sessionId]["assignedAgentId"] = claiming_agent_id
        active_sessions[payload.sessionId]["assignedAt"] = claimed_doc.get("assignedAt")

    site_id = session.get("siteId", "")

    # 5. Broadcast real-time claim updates
    await broadcast_to_agents({
        "type": "chat_claimed",
        "sessionId": payload.sessionId,
        "siteId": site_id,
        "status": "IN_CHAT",
        "assignedAgentId": claiming_agent_id
    })

    await broadcast_to_agents({
        "type": "visitor_accepted",
        "sessionId": payload.sessionId,
        "siteId": site_id,
        "status": "IN_CHAT",
        "assignedAgentId": claiming_agent_id
    })

    # 6. Notify visitor socket of status change to IN_CHAT
    await notify_visitor_socket(payload.sessionId, {
        "type": "status_changed",
        "status": "IN_CHAT",
        "assignedAgentId": claiming_agent_id
    })

    return HandoffActionResponse(
        sessionId=payload.sessionId,
        status="IN_CHAT",
        assignedAgentId=claiming_agent_id
    )

@router.post("/end", status_code=status.HTTP_200_OK)
async def end_chat(payload: HandoffActionRequest):
    db = get_database()
    session = await get_session_by_id(db, payload.sessionId)
    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Session not found for sessionId '{payload.sessionId}'"
        )

    ended_by = payload.endedBy if payload.endedBy in ("visitor", "agent") else "visitor"

    # 1. Transition state to ENDED cleanly in DB
    await update_session_status(db, payload.sessionId, "ENDED")
    update_active_session_status(payload.sessionId, "ENDED", ended_by=ended_by)

    # 2. Update conversation status in DB
    site_id = session.get("siteId", "")
    conversation = await get_or_create_conversation(db, payload.sessionId, site_id)
    await db["conversations"].update_one(
        {"conversationId": conversation["conversationId"]},
        {"$set": {"status": "ended", "endedBy": ended_by}}
    )

    # 3. Broadcast updates to agents and visitor while socket is still active in memory
    await broadcast_to_agents({
        "type": "chat_ended",
        "sessionId": payload.sessionId,
        "siteId": site_id,
        "status": "ENDED",
        "endedBy": ended_by
    })
    await notify_visitor_socket(payload.sessionId, {
        "type": "status_changed",
        "status": "ENDED",
        "endedBy": ended_by
    })

    from app.services.video_signaling import terminate_video_call
    await terminate_video_call(payload.sessionId, reason="chat_ended")

    # 4. Cancel any pending grace-period timer, but keep the active_sessions
    #    entry (and its WebSocket reference) alive so that the visitor can
    #    still receive events (e.g. after clicking "Start New Chat").
    #    The entry is cleaned up when the WebSocket actually disconnects
    #    (handled in websocket.py's disconnect handler).
    cancel_grace_period_timer(payload.sessionId)

    # 5. Dispatch complete conversation transcript email asynchronously
    try:
        from app.services.email_service import send_conversation_transcript_email
        await send_conversation_transcript_email(db, payload.sessionId, ended_by=ended_by)
    except Exception as email_err:
        import logging
        logging.getLogger(__name__).error(
            f"Error triggering transcript email on end_chat for {payload.sessionId}: {email_err}",
            exc_info=True
        )

    return {"sessionId": payload.sessionId, "status": "ENDED", "endedBy": ended_by}

@router.post("/message", status_code=status.HTTP_200_OK)
async def send_agent_message(
    payload: AgentMessageRequest,
    authenticated_agent: dict | None = Depends(get_current_agent_optional)
):
    db = get_database()

    # Step 1: Validate that the session exists in MongoDB
    session = await get_session_by_id(db, payload.sessionId)
    if not session:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Session not found for sessionId '{payload.sessionId}'"
        )

    site_id = session.get("siteId", "")

    # Step 2: Check session status (memory or DB)
    in_memory_session = active_sessions.get(payload.sessionId, {})
    in_memory_status = in_memory_session.get("status")
    db_status = session.get("status")
    current_status = in_memory_status or db_status

    if current_status == "ENDED":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot send message to an ended session '{payload.sessionId}'"
        )

    # Step 3: Validate ownership if claimed
    claiming_agent = payload.agentId
    if authenticated_agent and authenticated_agent.get("agentId"):
        claiming_agent = authenticated_agent["agentId"]

    assigned_agent = in_memory_session.get("assignedAgentId") or session.get("assignedAgentId")
    if assigned_agent and claiming_agent and assigned_agent != claiming_agent:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You do not own this chat session"
        )

    # Step 4: Get or create conversation (One Session = One Conversation)
    conversation = await get_or_create_conversation(db, payload.sessionId, site_id)
    conversation_id = conversation["conversationId"]

    # Step 5: Persist AGENT message in MongoDB with the sending agent's ID
    sending_agent_id = claiming_agent or assigned_agent or (authenticated_agent.get("agentId") if authenticated_agent else None) or session.get("assignedAgentId")
    msg_doc = await create_message(
        db,
        conversation_id=conversation_id,
        session_id=payload.sessionId,
        role="agent",
        content=payload.message,
        agent_id=sending_agent_id
    )

    # Step 6: Notify Visitor via WebSocket
    await notify_visitor_socket(payload.sessionId, {
        "type": "agent_message",
        "sessionId": payload.sessionId,
        "messageId": msg_doc["messageId"],
        "message": payload.message
    })

    # Step 7: Broadcast Agent message to all connected Agent Dashboards
    await broadcast_to_agents({
        "type": "agent_message",
        "sessionId": payload.sessionId,
        "siteId": site_id,
        "assignedAgentId": assigned_agent or claiming_agent,
        "messageId": msg_doc["messageId"],
        "message": payload.message
    })

    return {"sessionId": payload.sessionId, "reply": payload.message}
