import net from 'net';
import fs from 'fs';
import path from 'path';

const DEFAULT_CONFIG = { host: 'localhost', port: 1235 };

function loadBridgeConfig() {
    try {
        const cfgPath = path.resolve(process.cwd(), 'backend/config/chatBridge.json');
        if (fs.existsSync(cfgPath)) {
            const raw = fs.readFileSync(cfgPath, 'utf-8');
            const json = JSON.parse(raw);
            return { ...DEFAULT_CONFIG, ...json };
        }
    } catch (e) {
        console.warn('Failed to load chatBridge.json, using defaults:', e.message);
    }
    return { ...DEFAULT_CONFIG };
}

export default class ChatTcpBridge {
    constructor(ioSocket, user) {
        this.ioSocket = ioSocket;
        this.user = user || {};
        this.token = null;
        this.tcp = null;
        this.buffer = '';
        this.config = loadBridgeConfig();
        this.pending = [];
        this.objStart = -1;
        this.depthCurly = 0;
        this.depthSquare = 0;
        this.inString = false;
        this.escapeNext = false;
    }

    connect(initialRoomName = '') {
        if (this.tcp) return;
        const { host, port } = this.config;

        this.ioEmitStatus('connecting', `${host}:${port}`);
        console.log(`[ChatBridge] Connecting to ${host}:${port}`);
        this.tcp = new net.Socket();

        this.tcp.on('connect', () => {
            this.ioEmitStatus('connected', `${host}:${port}`);
            console.log(`[ChatBridge] Connected to ${host}:${port}`);
            // Erstnachricht: init
            const initPayload = {
                type: 'init',
                name: this.user.username || '',
                room: initialRoomName || '',
            };
            this.sendRaw(initPayload);
        });

        this.tcp.on('data', (data) => {
            this.processChunk(data.toString('utf-8'));
        });

        this.tcp.on('error', (err) => {
            this.ioEmitStatus('error', err.message);
            console.error('[ChatBridge] Error:', err.message);
        });

        this.tcp.on('close', () => {
            this.ioEmitStatus('disconnected');
            console.warn('[ChatBridge] Disconnected');
        });

        this.tcp.connect(port, host);
    }

    handleIncomingLine(line) {
        try {
            const obj = JSON.parse(line);
            this.handleIncoming(obj);
        } catch (e) {
            this.ioSocket.emit('chat:incoming', { type: 'system', text: line });
        }
    }

    processChunk(chunk) {
        if (!chunk) return;
        for (let i = 0; i < chunk.length; i++) {
            const ch = chunk[i];
            this.buffer += ch;
            if (this.escapeNext) {
                this.escapeNext = false;
                continue;
            }
            if (this.inString) {
                if (ch === '\\') { this.escapeNext = true; }
                else if (ch === '"') { this.inString = false; }
                continue;
            }
            if (ch === '"') { this.inString = true; continue; }
            if (ch === '{') { this.depthCurly++; if (this.objStart === -1) this.objStart = this.buffer.length - 1; }
            else if (ch === '}') { this.depthCurly--; }
            else if (ch === '[') { this.depthSquare++; }
            else if (ch === ']') { this.depthSquare--; }

            // Wenn ein komplettes Objekt abgeschlossen ist (außerhalb von Strings und beide Tiefen 0)
            if (this.objStart !== -1 && this.depthCurly === 0) {
                const jsonStr = this.buffer.slice(this.objStart, this.buffer.length);
                // Alles vor objStart (falls vorhanden) rausfiltern
                const prefix = this.buffer.slice(0, this.objStart);
                if (prefix.trim()) {
                    // Unerwarteter Text, als System ausgeben
                    this.ioSocket.emit('chat:incoming', { type: 'system', text: prefix.trim() });
                }
                this.buffer = '';
                this.objStart = -1;
                this.parseAndHandle(jsonStr);
            }
        }
    }

    parseAndHandle(jsonStr) {
        try {
            const obj = JSON.parse(jsonStr);
            // Forward raw parsed inbound message to browser console
            this.ioSocket.emit('chat:debug', { dir: 'in', payload: obj });
            this.handleIncoming(obj);
        } catch (e) {
            // Wenn mehrere Objekte direkt aneinander hängen (}{), versuchen sie zu splitten
            const parts = jsonStr.split('}{').map((p, idx, arr) => (idx === 0 ? p + '}' : (idx === arr.length - 1 ? '{' + p : '{' + p + '}')));
            if (parts.length > 1) {
                parts.forEach(p => {
                    try {
                        const o = JSON.parse(p);
                        this.ioSocket.emit('chat:debug', { dir: 'in', payload: o });
                        this.handleIncoming(o);
                    } catch (_) {
                        this.ioSocket.emit('chat:debug', { dir: 'in', raw: p });
                        this.ioSocket.emit('chat:incoming', { type: 'system', text: p });
                    }
                });
            } else {
                this.ioSocket.emit('chat:debug', { dir: 'in', raw: jsonStr });
                this.ioSocket.emit('chat:incoming', { type: 'system', text: jsonStr });
            }
        }
    }

    handleIncoming(obj) {
        if (!obj) return;
        // Map numeric type codes
        if (typeof obj.type === 'number') {
            switch (obj.type) {
                case 1: // token
                    if (typeof obj.message === 'string' && obj.message) {
                        this.token = obj.message;
                        this.ioSocket.emit('chat:incoming', { type: 'system', text: 'Token received' });
                        const actions = this.pending; this.pending = [];
                        actions.forEach(fn => { try { fn(); } catch (_) { } });
                        return;
                    }
                    break;
                case 3: // room list
                    if (Array.isArray(obj.message)) {
                        const names = obj.message.map(r => r.name).filter(Boolean).join(', ');
                        this.ioSocket.emit('chat:incoming', { type: 'system', text: names ? `Rooms: ${names}` : 'Rooms updated' });
                        return;
                    }
                    break;
                case 5: {
                    // generic server event (room entered, color changes, etc.)
                    const msg = obj.message;
                    if (!msg) break;
                    if (typeof msg === 'string') {
                        if (msg === 'room_entered') {
                            const to = obj.to || obj.name || obj.room || '';
                            this.ioSocket.emit('chat:incoming', { type: 'system', code: 'room_entered', tr: 'room_entered', to });
                            return;
                        }
                        if (msg === 'color_changed' || msg === 'user_color_changed') {
                            this.ioSocket.emit('chat:incoming', {
                                type: 'system',
                                code: msg,
                                color: obj.color,
                                userName: obj.userName || ''
                            });
                            return;
                        }
                        // unknown message string; fallthrough to text echo
                        this.ioSocket.emit('chat:incoming', { type: 'system', text: msg });
                        return;
                    }
                    if (typeof msg === 'object') {
                        const tr = msg.tr || 'room_entered';
                        const to = msg.to || msg.name || msg.room || '';
                        this.ioSocket.emit('chat:incoming', { type: 'system', code: tr, tr, to });
                        return;
                    }
                    break;
                }
                case 6: {
                    // scream
                    const userName = obj.userName || obj.user || obj.name || '';
                    const message = obj.message || '';
                    this.ioSocket.emit('chat:incoming', {
                        type: 'scream',
                        userName,
                        message,
                        color: obj.color || null
                    });
                    return;
                }
                default:
                    break;
            }
        }
        if (obj.type === 'token' && obj.message) {
            this.token = obj.message;
            this.ioSocket.emit('chat:incoming', { type: 'system', text: 'Token received' });
            // Ausstehende Aktionen jetzt mit Token senden
            const actions = this.pending;
            this.pending = [];
            actions.forEach(fn => {
                try { fn(); } catch (_) { }
            });
            return;
        }
        // Normalize generic messages
        if (typeof obj.message === 'string' && obj.message && (obj.userName || obj.user || obj.name)) {
            this.ioSocket.emit('chat:incoming', {
                type: 'message',
                message: obj.message,
                userName: obj.userName || obj.user || obj.name,
                color: obj.color || null
            });
            return;
        }
        // As system fallback
        this.ioSocket.emit('chat:incoming', { type: 'system', text: JSON.stringify(obj) });
    }

    sendRaw(obj) {
        if (!this.tcp) return;
        try {
            const str = JSON.stringify(obj) + '\n';
            // Forward outbound payload to browser console
            this.ioSocket.emit('chat:debug', { dir: 'out', payload: obj });
            this.tcp.write(str);
        } catch (e) {
            this.ioEmitStatus('error', e.message);
        }
    }

    sendWithToken(obj) {
        if (this.token) obj.token = this.token;
        this.sendRaw(obj);
    }

    withTokenOrQueue(fn) {
        if (this.token) {
            fn();
        } else {
            this.pending.push(fn);
        }
    }

    joinRoom(roomName, password = '') {
        // Prefer keys name/room if server expects them for room switch; fallback to newroom if required elsewhere
        this.withTokenOrQueue(() => this.sendWithToken({ type: 'join', room: roomName, name: this.user.username || '', password }));
    }

    sendMessage(text) {
        this.withTokenOrQueue(() => this.sendWithToken({ type: 'message', message: text, userName: this.user.username }));
    }

    scream(text) {
        this.withTokenOrQueue(() => this.sendWithToken({ type: 'scream', message: text }));
    }

    doAction(text) {
        this.withTokenOrQueue(() => this.sendWithToken({ type: 'do', message: text }));
    }

    dice(expr) {
        this.withTokenOrQueue(() => this.sendWithToken({ type: 'dice', message: expr || '' }));
    }

    color(hex) {
        let value = typeof hex === 'string' ? hex.trim() : '';
        if (!value) return;
        if (!value.startsWith('#')) value = '#' + value;
        // Normalize 3-digit to 6-digit when possible
        const m3 = /^#([0-9a-fA-F]{3})$/.exec(value);
        if (m3) {
            value = '#' + m3[1].split('').map(c => c + c).join('');
        }
        const m6 = /^#([0-9a-fA-F]{6})$/.exec(value);
        if (!m6) return;
        this.withTokenOrQueue(() => this.sendWithToken({ type: 'color', value }));
    }

    close() {
        try { this.tcp?.destroy(); } catch (_) { }
        this.tcp = null;
    }

    ioEmitStatus(type, detail) {
        this.ioSocket.emit('chat:status', { type, detail });
    }
}
