from datetime import datetime, timezone
from typing import Dict, Any
from app.services.presence import (
    active_sessions,
    broadcast_to_agents,
    notify_visitor_socket
)

# In-memory store of active video calls:
# { sessionId: { "sessionId": str, "state": str, "agentId": str | None, "updatedAt": str } }
# States: IDLE, CALL_REQUESTED, RINGING, ACCEPTED, CONNECTING, CONNECTED, REJECTED, ENDED
active_calls: Dict[str, Dict[str, Any]] = {}

VALID_STATES = {
    "IDLE",
    "CALL_REQUESTED",
    "RINGING",
    "ACCEPTED",
    "CONNECTING",
    "CONNECTED",
    "REJECTED",
    "ENDED"
}

def get_call_state(session_id: str) -> str:
    """Returns the current video call state for a session, defaulting to IDLE."""
    call = active_calls.get(session_id)
    return call.get("state", "IDLE") if call else "IDLE"

def set_call_state(session_id: str, state: str, agent_id: str | None = None) -> None:
    """Updates the in-memory video call state machine."""
    if state not in VALID_STATES:
        return
    now_iso = datetime.now(timezone.utc).isoformat()
    if state in ("IDLE", "ENDED", "REJECTED"):
        active_calls.pop(session_id, None)
    else:
        active_calls[session_id] = {
            "sessionId": session_id,
            "state": state,
            "agentId": agent_id,
            "updatedAt": now_iso
        }

async def handle_visitor_signaling(session_id: str, payload: dict) -> None:
    """
    Handles WebRTC signaling messages dispatched from a visitor WebSocket.
    Validates session state and forwards payloads to the assigned agent.
    """
    msg_type = payload.get("type")
    if not msg_type:
        return

    print(f"[VIDEO DEBUG] visitor video event received: type={msg_type}, sessionId={session_id}")
    print(f"[VIDEO DEBUG] sessionId={session_id}")

    # Check active session existence and status
    session = active_sessions.get(session_id)
    status_val = session.get("status") if session else None
    assigned_agent_id = session.get("assignedAgentId") if session else None

    # Fallback to database if missing from memory
    if not status_val or status_val != "IN_CHAT" or not assigned_agent_id:
        from app.database import get_database
        from app.repositories.session_repository import get_session_by_id
        db = get_database()
        if db is not None:
            db_session = await get_session_by_id(db, session_id)
            if db_session:
                status_val = db_session.get("status")
                assigned_agent_id = db_session.get("assignedAgentId")
                if session:
                    session["status"] = status_val
                    session["assignedAgentId"] = assigned_agent_id

    print(f"[VIDEO DEBUG] assignedAgentId={assigned_agent_id}")

    if status_val != "IN_CHAT":
        print(f"[VIDEO DEBUG] Rejected call for {session_id}: status is {status_val} (not IN_CHAT)")
        await notify_visitor_socket(session_id, {
            "type": "video_call_rejected",
            "sessionId": session_id,
            "reason": "Video calling requires an active chat with an agent"
        })
        return

    if not assigned_agent_id:
        print(f"[VIDEO DEBUG] Rejected call for {session_id}: no assigned agent")
        await notify_visitor_socket(session_id, {
            "type": "video_call_rejected",
            "sessionId": session_id,
            "reason": "No agent assigned to this session"
        })
        return

    current_state = get_call_state(session_id)

    if msg_type in ("video_call_requested", "webrtc_offer"):
        if current_state in ("CALL_REQUESTED", "RINGING", "ACCEPTED", "CONNECTING", "CONNECTED"):
            await notify_visitor_socket(session_id, {
                "type": "video_call_rejected",
                "sessionId": session_id,
                "reason": "A video call is already in progress"
            })
            return

        set_call_state(session_id, "CALL_REQUESTED", assigned_agent_id)
        forward_payload = {
            "type": "video_call_requested",
            "sessionId": session_id,
            "siteId": session.get("siteId") if session else "",
            "user": session.get("user") if session else None,
            "offer": payload.get("offer") or payload.get("sdp"),
            "sender": "visitor"
        }
        print(f"[VIDEO] visitor request received for session {session_id}")
        print(f"[VIDEO] assigned agent = {assigned_agent_id}")
        print(f"[VIDEO] forwarding request to agent {assigned_agent_id}")
        await broadcast_to_agents(forward_payload, target_agent_ids=[assigned_agent_id])

    elif msg_type in ("video_call_accepted", "webrtc_answer"):
        set_call_state(session_id, "ACCEPTED", assigned_agent_id)
        forward_payload = {
            "type": "webrtc_answer",
            "sessionId": session_id,
            "answer": payload.get("answer") or payload.get("sdp")
        }
        print(f"[VIDEO] forwarding answer to agent {assigned_agent_id}")
        await broadcast_to_agents(forward_payload, target_agent_ids=[assigned_agent_id])

    elif msg_type == "video_call_rejected":
        set_call_state(session_id, "REJECTED")
        forward_payload = {
            "type": "video_call_rejected",
            "sessionId": session_id,
            "reason": payload.get("reason", "Visitor declined the video call")
        }
        await broadcast_to_agents(forward_payload, target_agent_ids=[assigned_agent_id])

    elif msg_type == "webrtc_ice_candidate":
        forward_payload = {
            "type": "webrtc_ice_candidate",
            "sessionId": session_id,
            "candidate": payload.get("candidate"),
            "target": "agent"
        }
        print(f"[VIDEO] forwarding ICE candidate to agent {assigned_agent_id}")
        await broadcast_to_agents(forward_payload, target_agent_ids=[assigned_agent_id])

    elif msg_type == "video_call_ended":
        set_call_state(session_id, "ENDED")
        forward_payload = {
            "type": "video_call_ended",
            "sessionId": session_id,
            "endedBy": "visitor"
        }
        await broadcast_to_agents(forward_payload, target_agent_ids=[assigned_agent_id])

async def handle_agent_signaling(agent_id: str, payload: dict) -> None:
    """
    Handles WebRTC signaling messages dispatched from an agent WebSocket.
    Validates agent assignment and forwards payloads to the visitor WebSocket.
    """
    msg_type = payload.get("type")
    session_id = payload.get("sessionId")
    print(f"[VIDEO][TRACE][BACKEND] received agent event")
    print(f"[VIDEO][TRACE][BACKEND] type = {msg_type}")
    print(f"[VIDEO][TRACE][BACKEND] sessionId = {session_id}")
    print(f"[VIDEO][TRACE][BACKEND] agentId = {agent_id}")
    if not msg_type or not session_id:
        return

    session = active_sessions.get(session_id)
    assigned_agent_id = session.get("assignedAgentId") if session else None
    if not assigned_agent_id:
        from app.database import get_database
        from app.repositories.session_repository import get_session_by_id
        db = get_database()
        if db is not None:
            db_session = await get_session_by_id(db, session_id)
            if db_session:
                assigned_agent_id = db_session.get("assignedAgentId")
                if session:
                    session["assignedAgentId"] = assigned_agent_id

    print(f"[VIDEO][TRACE][BACKEND] session doc: sessionId={session_id}, assignedAgentId={assigned_agent_id}, status={session.get('status') if session else 'none'}")

    # Security check: If assigned_agent_id is set, only the assigned agent can signal for this session
    if assigned_agent_id and assigned_agent_id != agent_id:
        print(f"[VIDEO][TRACE][BACKEND] Security check failed: assigned={assigned_agent_id}, sender={agent_id} - dropping")
        return

    if msg_type in ("video_call_requested", "webrtc_offer"):
        set_call_state(session_id, "CALL_REQUESTED", agent_id)
        forward_payload = {
            "type": "video_call_requested",
            "sessionId": session_id,
            "offer": payload.get("offer") or payload.get("sdp"),
            "sender": "agent"
        }
        await notify_visitor_socket(session_id, forward_payload)

    elif msg_type in ("video_call_accepted", "webrtc_answer"):
        set_call_state(session_id, "ACCEPTED", agent_id)
        forward_payload = {
            "type": "webrtc_answer",
            "sessionId": session_id,
            "answer": payload.get("answer") or payload.get("sdp")
        }
        await notify_visitor_socket(session_id, forward_payload)

    elif msg_type == "video_call_rejected":
        set_call_state(session_id, "REJECTED")
        forward_payload = {
            "type": "video_call_rejected",
            "sessionId": session_id,
            "reason": payload.get("reason", "Agent declined the video call")
        }
        await notify_visitor_socket(session_id, forward_payload)

    elif msg_type == "webrtc_ice_candidate":
        forward_payload = {
            "type": "webrtc_ice_candidate",
            "sessionId": session_id,
            "candidate": payload.get("candidate"),
            "target": "visitor"
        }
        await notify_visitor_socket(session_id, forward_payload)

    elif msg_type == "video_call_ended":
        set_call_state(session_id, "ENDED")
        forward_payload = {
            "type": "video_call_ended",
            "sessionId": session_id,
            "endedBy": "agent"
        }
        await notify_visitor_socket(session_id, forward_payload)

    elif msg_type in ("agent_message", "message", "chat_message"):
        content = payload.get("message") or payload.get("content")
        if content:
            from app.repositories.conversation_repository import get_or_create_conversation
            from app.repositories.message_repository import create_message
            from app.services.presence import broadcast_to_agents
            from app.database import get_database
            db = get_database()
            site_id = session.get("siteId", "") if session else ""
            conversation = await get_or_create_conversation(db, session_id, site_id)
            conversation_id = conversation["conversationId"]
            msg_doc = await create_message(
                db,
                conversation_id=conversation_id,
                session_id=session_id,
                role="agent",
                content=content,
                agent_id=agent_id
            )
            await notify_visitor_socket(session_id, {
                "type": "agent_message",
                "sessionId": session_id,
                "messageId": msg_doc["messageId"],
                "message": content
            })
            await broadcast_to_agents({
                "type": "agent_message",
                "sessionId": session_id,
                "siteId": site_id,
                "assignedAgentId": agent_id,
                "messageId": msg_doc["messageId"],
                "message": content
            })

async def terminate_video_call(session_id: str, reason: str = "chat_ended") -> None:
    """
    Cleans up any active video call for a session when chat ends or disconnects.
    """
    if session_id in active_calls:
        call_info = active_calls.pop(session_id, None)
        agent_id = call_info.get("agentId") if call_info else None

        end_event = {
            "type": "video_call_ended",
            "sessionId": session_id,
            "reason": reason
        }
        await notify_visitor_socket(session_id, end_event)
        if agent_id:
            await broadcast_to_agents(end_event, target_agent_ids=[agent_id])
        else:
            await broadcast_to_agents(end_event)
