import { useEffect, useCallback, useRef, type PropsWithChildren } from 'react';
import { useAtom, useSetAtom } from 'jotai';
import {
    sipConnectionStatusAtom,
    sipCallStateAtom,
    sipCallerNumberAtom,
    sipIsMutedAtom,
    sipIsOnHoldAtom,
    sipCallDurationAtom,
    sipCallStartTimeAtom,
    sipCallUcidAtom,
} from '@/state/sip-state';
import { registerSipStateUpdater, connectSip, disconnectSip, getSipClient, setOutboundPending } from '@/state/sip-manager';
import { apiClient } from '@/lib/api-client';
import { notify } from '@/lib/toast';
import type { SIPConfig } from '@/types/sip';

// SIP config comes exclusively from the Agent entity (stored on login).
// No env var fallback — users without an Agent entity don't connect SIP.
const getSipConfig = (): SIPConfig | null => {
    try {
        const stored = localStorage.getItem('helix_agent_config');
        if (stored) {
            const config = JSON.parse(stored);
            if (config.sipUri && config.sipWsServer) {
                return {
                    displayName: 'Helix Agent',
                    uri: config.sipUri,
                    password: config.sipPassword,
                    wsServer: config.sipWsServer,
                    stunServers: 'stun:stun.l.google.com:19302',
                };
            }
        }
    } catch {}
    return null;
};

export const SipProvider = ({ children }: PropsWithChildren) => {
    const [, setConnectionStatus] = useAtom(sipConnectionStatusAtom);
    const [callState, setCallState] = useAtom(sipCallStateAtom);
    const setCallerNumber = useSetAtom(sipCallerNumberAtom);
    const setCallUcid = useSetAtom(sipCallUcidAtom);
    const setCallDuration = useSetAtom(sipCallDurationAtom);
    const setCallStartTime = useSetAtom(sipCallStartTimeAtom);
    const setIsMutedGlobal = useSetAtom(sipIsMutedAtom);
    const setIsOnHoldGlobal = useSetAtom(sipIsOnHoldAtom);

    // Register Jotai setters so the singleton SIP manager can update atoms
    useEffect(() => {
        registerSipStateUpdater({
            setConnectionStatus,
            setCallState,
            setCallerNumber,
            setCallUcid,
            setIsMuted: setIsMutedGlobal,
            setIsOnHold: setIsOnHoldGlobal,
        });
    }, [setConnectionStatus, setCallState, setCallerNumber, setCallUcid, setIsMutedGlobal, setIsOnHoldGlobal]);

    // Auto-connect SIP on mount — only if Agent entity has SIP config
    useEffect(() => {
        const config = getSipConfig();
        if (config) {
            connectSip(config);
        } else {
            console.log('[SIP] No agent SIP config — skipping connection');
        }
    }, []);

    // Call duration timer
    useEffect(() => {
        if (callState === 'active') {
            const start = new Date();
            setCallStartTime(start);
            const interval = window.setInterval(() => {
                setCallDuration(Math.floor((Date.now() - start.getTime()) / 1000));
            }, 1000);
            return () => clearInterval(interval);
        }
        setCallDuration(0);
        setCallStartTime(null);
    }, [callState, setCallDuration, setCallStartTime]);

    // Ringtone on incoming call
    useEffect(() => {
        if (callState === 'ringing-in') {
            import('@/lib/ringtone').then(({ startRingtone }) => startRingtone());
        } else {
            import('@/lib/ringtone').then(({ stopRingtone }) => stopRingtone());
        }
    }, [callState]);

    // No auto-reset — the ActiveCallCard handles post-call flow (disposition → appointment → done)
    // and resets to idle via the "Back to Worklist" button

    // Layer 1: Warn on page refresh/close during active call
    // Layer 2: Fire sendBeacon to auto-dispose if user confirms leave
    // These two layers protect against the "agent refreshes mid-call → stuck in ACW" bug.
    // Layer 3 (server-side ACW timeout) lives in supervisor.service.ts.
    //
    // IMPORTANT: beforeunload reads callState via a ref (not the dep array)
    // because adding callState to deps causes the cleanup to fire on every
    // state transition → disconnectSip() → kills the call mid-flight.
    const callStateRef = useRef(callState);
    callStateRef.current = callState;

    useEffect(() => {
        const handleBeforeUnload = (e: BeforeUnloadEvent) => {
            const ucid = localStorage.getItem('helix_active_ucid');
            const state = callStateRef.current;

            // Layer 1: Show browser "Leave page?" dialog during active calls
            if (state === 'active' || state === 'ringing-in' || state === 'ringing-out') {
                e.preventDefault();
                e.returnValue = '';
            }

            // Layer 2: Fire disposition beacon if there's an active UCID
            if (ucid) {
                const agentCfg = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}');
                const payload = JSON.stringify({
                    ucid,
                    disposition: 'CALLBACK_REQUESTED',
                    agentId: agentCfg.ozonetelAgentId,
                    autoDisposed: true,
                });
                navigator.sendBeacon('/api/ozonetel/dispose', new Blob([payload], { type: 'application/json' }));
                localStorage.removeItem('helix_active_ucid');
            }
        };

        const handleUnload = () => disconnectSip(true, 'page-unload');

        window.addEventListener('beforeunload', handleBeforeUnload);
        window.addEventListener('unload', handleUnload);
        return () => {
            window.removeEventListener('beforeunload', handleBeforeUnload);
            window.removeEventListener('unload', handleUnload);
            disconnectSip(true, 'sip-provider-unmount'); // force — component is unmounting
        };
    }, []); // empty deps — runs once on mount, cleanup only on unmount

    return <>{children}</>;
};

// Hook for components to access SIP actions + state
export const useSip = () => {
    const [connectionStatus] = useAtom(sipConnectionStatusAtom);
    const [callState, setCallState] = useAtom(sipCallStateAtom);
    const [callerNumber, setCallerNumber] = useAtom(sipCallerNumberAtom);
    const [callUcid, setCallUcid] = useAtom(sipCallUcidAtom);
    const [isMuted, setIsMuted] = useAtom(sipIsMutedAtom);
    const [isOnHold, setIsOnHold] = useAtom(sipIsOnHoldAtom);
    const [callDuration] = useAtom(sipCallDurationAtom);

    const makeCall = useCallback((phoneNumber: string) => {
        getSipClient()?.call(phoneNumber);
        setCallerNumber(phoneNumber);
    }, [setCallerNumber]);

    // Ozonetel outbound dial — single path for all outbound calls
    const dialOutbound = useCallback(async (phoneNumber: string): Promise<void> => {
        // Hard guard — no dial is valid when SIP isn't registered, because
        // the audio leg can't be established. Every entry point (worklist
        // row, click-to-call, phone-action-cell, patient 360, etc.) funnels
        // through this callback, so gating here is the single source of
        // truth for "can this agent place a call right now?"
        if (connectionStatus !== 'registered') {
            notify.error('Telephony unavailable', 'Cannot place call — SIP is not registered. Check your connection.');
            console.warn(`[DIAL] Blocked — SIP not registered (status=${connectionStatus})`);
            return;
        }

        // Block outbound calls when agent is on Break or Training
        const agentCfg = localStorage.getItem('helix_agent_config');
        if (agentCfg) {
            const { useAgentState: _ } = await import('@/hooks/use-agent-state');
            // Read state from the SSE endpoint directly (can't use hook here)
            const agentId = JSON.parse(agentCfg).ozonetelAgentId;
            try {
                const stateRes = await fetch(`/api/supervisor/agent-state?agentId=${agentId}`);
                const stateData = await stateRes.json();
                if (stateData.state === 'break' || stateData.state === 'training') {
                    notify.info('Status: ' + stateData.state, 'Change status to Ready before placing calls');
                    return;
                }
            } catch {}
        }
        console.log(`[DIAL] Outbound dial started: phone=${phoneNumber}`);
        setCallState('ringing-out');
        setCallerNumber(phoneNumber);
        setOutboundPending(true);
        const safetyTimeout = setTimeout(() => {
            console.warn('[DIAL] Safety timeout fired (30s) — clearing outboundPending');
            setOutboundPending(false);
        }, 30000);

        try {
            // Send agent config so the sidecar dials with the correct agent ID + campaign
            const agentConfig = JSON.parse(localStorage.getItem('helix_agent_config') ?? '{}');
            const result = await apiClient.post<{ status: string; ucid?: string }>('/api/ozonetel/dial', {
                phoneNumber,
                agentId: agentConfig.ozonetelAgentId,
                campaignName: agentConfig.campaignName,
            });
            console.log('[DIAL] Dial API response:', result);
            clearTimeout(safetyTimeout);
            // Store UCID from dial response — SIP bridge doesn't carry X-UCID for outbound
            if (result?.ucid) {
                console.log(`[DIAL] Storing UCID from dial response: ${result.ucid}`);
                setCallUcid(result.ucid);
            }
        } catch (err) {
            console.error('[DIAL] Dial API failed:', err);
            clearTimeout(safetyTimeout);
            setOutboundPending(false);
            setCallState('idle');
            setCallerNumber(null);
            throw new Error('Dial failed');
        }
    }, [setCallState, setCallerNumber, setCallUcid, connectionStatus]);

    const answer = useCallback(() => getSipClient()?.answer(), []);
    const reject = useCallback(() => getSipClient()?.reject(), []);
    const hangup = useCallback(() => getSipClient()?.hangup(), []);

    const toggleMute = useCallback(() => {
        if (isMuted) {
            getSipClient()?.unmute();
        } else {
            getSipClient()?.mute();
        }
        setIsMuted(!isMuted);
    }, [isMuted, setIsMuted]);

    const toggleHold = useCallback(() => {
        if (isOnHold) {
            getSipClient()?.unhold();
        } else {
            getSipClient()?.hold();
        }
        setIsOnHold(!isOnHold);
    }, [isOnHold, setIsOnHold]);

    return {
        connectionStatus,
        callState,
        callerNumber,
        callUcid,
        isMuted,
        isOnHold,
        callDuration,
        isRegistered: connectionStatus === 'registered',
        isInCall: ['ringing-in', 'ringing-out', 'active'].includes(callState),
        ozonetelStatus: 'logged-in' as const,
        ozonetelError: null as string | null,
        connect: () => { const c = getSipConfig(); if (c) connectSip(c); },
        disconnect: disconnectSip,
        makeCall,
        dialOutbound,
        answer,
        reject,
        hangup,
        toggleMute,
        toggleHold,
    };
};
