import JsSIP from 'jssip';
import type { UAConfiguration, RTCSessionEvent, CallOptions } from 'jssip/lib/UA';
import type { RTCSession, PeerConnectionEvent, EndEvent, CallListener } from 'jssip/lib/RTCSession';
import type { SIPConfig, ConnectionStatus, CallState } from '@/types/sip';

export class SIPClient {
    private ua: JsSIP.UA | null = null;
    private currentSession: RTCSession | null = null;
    private audioElement: HTMLAudioElement | null = null;
    // Watchdog that alerts if REGISTER never completes after a connect.
    // Cleared on 'registered' / 'registrationFailed' / disconnect.
    private registrationWatchdog: number | null = null;
    private readonly REGISTRATION_TIMEOUT_MS = 15_000;

    constructor(
        private config: SIPConfig,
        private onConnectionChange: (status: ConnectionStatus) => void,
        private onCallStateChange: (state: CallState, callerNumber?: string, ucid?: string) => void,
    ) {}

    connect(): void {
        // Disable verbose JsSIP protocol logging — we log lifecycle events ourselves
        JsSIP.debug.disable('JsSIP:*');

        const socket = new JsSIP.WebSocketInterface(this.config.wsServer);
        const sipId = this.config.uri.replace('sip:', '').split('@')[0];

        const configuration: UAConfiguration = {
            sockets: [socket],
            uri: this.config.uri,
            password: this.config.password,
            authorization_user: sipId,
            display_name: this.config.displayName,
            register: true,
            register_expires: 120,
            session_timers: false,
            connection_recovery_min_interval: 2,
            connection_recovery_max_interval: 30,
        };

        this.ua = new JsSIP.UA(configuration);

        console.log(`[SIP] start() uri=${this.config.uri} ws=${this.config.wsServer} expires=${configuration.register_expires}s`);

        this.ua.on('connecting', () => {
            console.log('[SIP] WebSocket connecting…');
        });

        this.ua.on('connected', () => {
            console.log('[SIP] WebSocket connected — waiting for REGISTER');
            this.onConnectionChange('connected');
        });

        this.ua.on('disconnected', (e: any) => {
            const code = e?.code ?? 'n/a';
            const reason = e?.reason ?? 'unknown';
            console.log(`[SIP] WebSocket disconnected — code=${code} reason=${reason}`);
            this.clearRegistrationWatchdog();
            this.onConnectionChange('disconnected');
        });

        this.ua.on('registered', () => {
            console.log('[SIP] Registered successfully');
            this.clearRegistrationWatchdog();
            this.onConnectionChange('registered');
        });

        this.ua.on('unregistered', () => {
            console.log('[SIP] Unregistered');
            this.clearRegistrationWatchdog();
            this.onConnectionChange('disconnected');
        });

        this.ua.on('registrationFailed', (e: any) => {
            const cause = e?.cause ?? 'unknown';
            const statusCode = e?.response?.status_code ?? 'n/a';
            const reasonPhrase = e?.response?.reason_phrase ?? '';
            console.error(`[SIP] Registration failed — cause=${cause} status=${statusCode} ${reasonPhrase}`);
            this.clearRegistrationWatchdog();
            this.onConnectionChange('error');
        });

        this.ua.on('newRTCSession', (data: RTCSessionEvent) => {
            const session = data.session;

            // If we already have an active session, reject the new one
            if (this.currentSession && this.currentSession !== session) {
                session.terminate();
                return;
            }

            this.currentSession = session;

            // Extract caller number and UCID — try event request first, then session
            const sipRequest = (data as any).request ?? (session as any)._request ?? null;
            const callerNumber = this.extractCallerNumber(session, sipRequest);
            const ucid = sipRequest?.getHeader ? sipRequest.getHeader('X-UCID') ?? null : null;

            console.log(`[SIP] New session: direction=${session.direction} caller=${callerNumber} ucid=${ucid ?? 'none'}`);

            // Setup audio for this session
            session.on('peerconnection', (e: PeerConnectionEvent) => {
                const pc = e.peerconnection;
                pc.ontrack = (event: RTCTrackEvent) => {
                    if (!this.audioElement) {
                        this.audioElement = document.createElement('audio');
                        this.audioElement.autoplay = true;
                        document.body.appendChild(this.audioElement);
                    }
                    this.audioElement.srcObject = event.streams[0];
                };
            });

            session.on('accepted', (() => {
                console.log(`[SIP] Call accepted — ucid=${ucid ?? 'none'}`);
                this.onCallStateChange('active', callerNumber, ucid ?? undefined);
            }) as CallListener);

            session.on('confirmed', () => {
                this.onCallStateChange('active', callerNumber, ucid ?? undefined);
            });

            session.on('progress', (() => {
                if (session.direction === 'outgoing') {
                    this.onCallStateChange('ringing-out', callerNumber, ucid ?? undefined);
                }
            }) as CallListener);

            session.on('failed', (e: EndEvent) => {
                console.log(`[SIP] Call failed — cause=${(e as any).cause ?? 'unknown'} ucid=${ucid ?? 'none'}`);
                this.resetSession();
                this.onCallStateChange('failed');
            });

            session.on('ended', (e: EndEvent) => {
                console.log(`[SIP] Call ended — cause=${(e as any).cause ?? 'normal'} ucid=${ucid ?? 'none'}`);
                this.resetSession();
                this.onCallStateChange('ended');
            });

            if (session.direction === 'incoming') {
                this.onCallStateChange('ringing-in', callerNumber, ucid ?? undefined);
            }
        });

        this.ua.start();

        // Arm the registration watchdog. If we don't hear 'registered' or
        // 'registrationFailed' within the timeout, surface a visible error so
        // the user isn't left staring at "Connecting to telephony…" forever.
        this.registrationWatchdog = window.setTimeout(() => {
            console.error(`[SIP] Registration timeout — no REGISTER response after ${this.REGISTRATION_TIMEOUT_MS}ms. Check SIP credentials / WebSocket reachability.`);
            this.onConnectionChange('error');
        }, this.REGISTRATION_TIMEOUT_MS);
    }

    private clearRegistrationWatchdog(): void {
        if (this.registrationWatchdog !== null) {
            window.clearTimeout(this.registrationWatchdog);
            this.registrationWatchdog = null;
        }
    }

    disconnect(): void {
        this.clearRegistrationWatchdog();
        this.hangup();
        if (this.ua) {
            this.ua.stop();
            this.ua = null;
        }
        this.cleanupAudio();
    }

    call(phoneNumber: string): void {
        if (!this.ua || !this.ua.isRegistered()) {
            throw new Error('SIP not registered');
        }

        const host = this.config.uri.split('@')[1];
        const target = `sip:${phoneNumber}@${host}`;

        const options: CallOptions = {
            mediaConstraints: { audio: true, video: false },
            pcConfig: {
                iceServers: this.parseStunServers(this.config.stunServers),
                iceTransportPolicy: 'all',
            },
            rtcOfferConstraints: {
                offerToReceiveAudio: true,
                offerToReceiveVideo: false,
            },
        };

        this.ua.call(target, options);
    }

    answer(): void {
        if (this.currentSession && this.currentSession.direction === 'incoming') {
            this.currentSession.answer({
                mediaConstraints: { audio: true, video: false },
                pcConfig: {
                    iceServers: this.parseStunServers(this.config.stunServers),
                    iceTransportPolicy: 'all',
                },
            });
        }
    }

    reject(): void {
        if (this.currentSession && this.currentSession.direction === 'incoming') {
            // Use 486 Busy Here for rejecting incoming calls
            this.currentSession.terminate({
                status_code: 486,
                reason_phrase: 'Busy Here',
            });
            this.resetSession();
            this.onCallStateChange('ended');
        }
    }

    hangup(): void {
        if (this.currentSession) {
            this.currentSession.terminate();
            this.resetSession();
        }
    }

    mute(): void {
        if (this.currentSession) {
            this.currentSession.mute({ audio: true });
        }
    }

    unmute(): void {
        if (this.currentSession) {
            this.currentSession.unmute({ audio: true });
        }
    }

    hold(): void {
        if (this.currentSession) {
            this.currentSession.hold();
        }
    }

    unhold(): void {
        if (this.currentSession) {
            this.currentSession.unhold();
        }
    }

    isConnected(): boolean {
        return this.ua?.isConnected() ?? false;
    }

    isRegistered(): boolean {
        return this.ua?.isRegistered() ?? false;
    }

    getAudioElement(): HTMLAudioElement | null {
        return this.audioElement;
    }

    private resetSession(): void {
        this.currentSession = null;
        this.cleanupAudio();
    }

    private cleanupAudio(): void {
        if (this.audioElement) {
            this.audioElement.srcObject = null;
            this.audioElement.remove();
            this.audioElement = null;
        }
    }

    private extractCallerNumber(session: RTCSession, sipRequest?: any): string {
        try {
            const request = sipRequest ?? (session.direction === 'incoming' ? (session as any)._request : null);
            if (request) {
                // Ozonetel sends the real caller number in X-CALLERNO header
                const xCallerNo = request.getHeader ? request.getHeader('X-CALLERNO') : null;
                if (xCallerNo) {
                    // Remove leading 0s or country code prefix (00919... → 919...)
                    const cleaned = xCallerNo.replace(/^0+/, '');
                    return cleaned;
                }

                // Check P-Asserted-Identity
                const pai = request.getHeader('P-Asserted-Identity');
                if (pai) {
                    const match = pai.match(/sip:(\+?\d+)@/);
                    if (match) return match[1];
                }

                // Check Remote-Party-ID
                const rpid = request.getHeader('Remote-Party-ID');
                if (rpid) {
                    const match = rpid.match(/sip:(\+?\d+)@/);
                    if (match) return match[1];
                }

                // Check X-Original-CallerID
                const xCid = request.getHeader('X-Original-CallerID');
                if (xCid) return xCid;

                // Check From header display name
                const fromDisplay = request.from?.display_name;
                if (fromDisplay && /^\+?\d{7,}$/.test(fromDisplay)) {
                    return fromDisplay;
                }
            }
        } catch (e) {
            console.warn('Error extracting caller ID from SIP headers:', e);
        }

        // Fallback to remote_identity URI
        const remoteUri = session.remote_identity?.uri?.toString() ?? '';
        const number = remoteUri.replace('sip:', '').split('@')[0] || 'Unknown';
        return number;
    }

    private parseStunServers(stunConfig: string): RTCIceServer[] {
        const servers: RTCIceServer[] = [];
        const lines = stunConfig.split('\n').filter((line) => line.trim());

        for (const line of lines) {
            const parts = line.split(',');
            const urls = parts[0].trim();

            if (parts.length === 3) {
                servers.push({ urls: [urls], username: parts[1].trim(), credential: parts[2].trim() });
            } else {
                servers.push({ urls: [urls] });
            }
        }

        return servers;
    }
}
