/**
 * worker.ts — WhatsApp worker entrypoint
 *
 * Exposes a small HTTP API on port 3002 for the main API to:
 *   POST   /sessions         — start/restore a Baileys session
 *   DELETE /sessions/:id     — terminate a session
 *   GET    /health           — health check
 *
 * Inbound messages are forwarded to the main API via /internal/messages.
 */
import 'dotenv/config';
import http from 'http';
import { clearAuthState, createBaileysSession } from './session.js';
import type { ActiveSession } from './session.js';
import { convertWebmToOggOpus } from './audioConvert.js';

const PORT       = Number(process.env.PORT ?? 3002);
const API_URL    = process.env.API_INTERNAL_URL ?? 'http://api:3001';

const sessions = new Map<string, ActiveSession>();

async function waitForApi(retries = 20): Promise<void> {
  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(`${API_URL}/health`);
      if (res.ok) { console.log('[Worker] API is ready'); return; }
    } catch { /* not ready yet */ }
    console.log(`[Worker] Waiting for API… (${i + 1}/${retries})`);
    await new Promise(r => setTimeout(r, 3000));
  }
  throw new Error('[Worker] API did not become ready in time');
}

function json(res: http.ServerResponse, status: number, body: unknown) {
  const payload = JSON.stringify(body);
  res.writeHead(status, { 'Content-Type': 'application/json' });
  res.end(payload);
}

async function readBody(req: http.IncomingMessage): Promise<string> {
  return new Promise(resolve => {
    let data = '';
    req.on('data', c => { data += c; });
    req.on('end', () => resolve(data));
  });
}

const server = http.createServer(async (req, res) => {
  const url    = req.url ?? '/';
  const method = req.method ?? 'GET';

  // GET /health
  if (method === 'GET' && url === '/health') {
    return json(res, 200, { status: 'ok', sessions: sessions.size });
  }

  // POST /sessions  — start a session ({ channelId, clearAuth?: boolean })
  if (method === 'POST' && url === '/sessions') {
    try {
      const body = await readBody(req);
      const { channelId, clearAuth } = JSON.parse(body) as { channelId: string; clearAuth?: boolean };
      if (!channelId) return json(res, 400, { error: 'channelId required' });

      // Reconnect / re-pair: drop any live socket and wipe creds so Baileys emits a fresh QR
      if (clearAuth) {
        const existing = sessions.get(channelId);
        if (existing) {
          // logout → loggedOut (no auto-reconnect); end(undefined) would schedule a 5s restart
          try { await existing.socket.logout(); } catch { /* ignore */ }
          sessions.delete(channelId);
        }
        clearAuthState(channelId);
      } else if (sessions.has(channelId)) {
        console.log(`[Worker] Session already active for ${channelId}`);
        return json(res, 200, { ok: true, existing: true });
      }

      const session = await createBaileysSession({
        channelId,
        apiUrl: API_URL,
        onReconnect: (newSession) => {
          sessions.set(channelId, newSession);
          console.log(`[Worker] Session reconnected and updated for ${channelId}`);
        },
        onLoggedOut: (id) => {
          sessions.delete(id);
          console.log(`[Worker] Removed logged-out session ${id}`);
        },
      });
      sessions.set(channelId, session);
      console.log(`[Worker] Started session for ${channelId}`);
      return json(res, 201, { ok: true });
    } catch (err) {
      console.error('[Worker] Failed to start session:', err);
      return json(res, 500, { error: String(err) });
    }
  }

  // POST /send  — text, image, or voice note (JSON; media as base64)
  if (method === 'POST' && url === '/send') {
    try {
      const raw = await readBody(req);
      const payload = JSON.parse(raw) as {
        channelId: string;
        phone: string;
        text?: string;
        kind?: 'text' | 'image' | 'voice_note' | 'document';
        mediaBase64?: string;
        mediaMime?: string;
        caption?: string;
        fileName?: string;
      };
      const { channelId, phone } = payload;
      const kind = payload.kind ?? 'text';

      if (!channelId || !phone) return json(res, 400, { error: 'channelId and phone are required' });

      const session = sessions.get(channelId);
      if (!session) return json(res, 404, { error: 'No active session for this channel' });

      const jid =
        phone.includes('@')
          ? phone
          : phone.replace(/^\+/, '') + '@s.whatsapp.net';

      if (kind === 'text') {
        const text = payload.text?.trim() ?? '';
        if (!text) return json(res, 400, { error: 'text is required for text messages' });
        const sent = await session.socket.sendMessage(jid, { text });
        console.log(`[Worker] Sent text to ${jid} via channel ${channelId}: ${sent?.key?.id}`);
        return json(res, 200, { ok: true, waMessageId: sent?.key?.id ?? null });
      }

      if (kind === 'image') {
        const b64 = payload.mediaBase64?.trim();
        const mime = payload.mediaMime?.trim() || 'image/jpeg';
        if (!b64) return json(res, 400, { error: 'mediaBase64 is required for image' });
        const buffer = Buffer.from(b64, 'base64');
        const caption = payload.caption?.trim() || payload.text?.trim();
        const sent = await session.socket.sendMessage(jid, { image: buffer, mimetype: mime, caption });
        console.log(`[Worker] Sent image to ${jid} via channel ${channelId}: ${sent?.key?.id}`);
        return json(res, 200, { ok: true, waMessageId: sent?.key?.id ?? null });
      }

      if (kind === 'voice_note') {
        const b64 = payload.mediaBase64?.trim();
        let mime = payload.mediaMime?.trim() || 'audio/ogg; codecs=opus';
        if (!b64) return json(res, 400, { error: 'mediaBase64 is required for voice_note' });
        let buffer = Buffer.from(b64, 'base64');
        // Browser MediaRecorder emits webm; WhatsApp PTT expects ogg/opus for reliable delivery
        if (mime.includes('webm')) {
          try {
            buffer = await convertWebmToOggOpus(buffer);
            mime = 'audio/ogg; codecs=opus';
          } catch (e) {
            console.warn('[Worker] webm→ogg conversion failed, sending original:', e);
          }
        }
        const sent = await session.socket.sendMessage(jid, {
          audio: buffer,
          mimetype: mime,
          ptt: true,
        });
        console.log(`[Worker] Sent voice note to ${jid} via channel ${channelId}: ${sent?.key?.id}`);
        return json(res, 200, { ok: true, waMessageId: sent?.key?.id ?? null });
      }

      if (kind === 'document') {
        const b64 = payload.mediaBase64?.trim();
        const mime = payload.mediaMime?.trim() || 'application/octet-stream';
        const fileName = (payload.fileName?.trim() || 'document').replace(/[/\\]/g, '_').slice(0, 200);
        if (!b64) return json(res, 400, { error: 'mediaBase64 is required for document' });
        const buffer = Buffer.from(b64, 'base64');
        const sent = await session.socket.sendMessage(jid, {
          document: buffer,
          mimetype: mime,
          fileName,
        });
        console.log(`[Worker] Sent document to ${jid} via channel ${channelId}: ${sent?.key?.id}`);
        return json(res, 200, { ok: true, waMessageId: sent?.key?.id ?? null });
      }

      return json(res, 400, { error: 'Invalid kind' });
    } catch (err) {
      console.error('[Worker] Failed to send message:', err);
      return json(res, 500, { error: String(err) });
    }
  }

  // POST /group/create — Baileys groupCreate({ subject, participants })
  if (method === 'POST' && url === '/group/create') {
    try {
      const raw = await readBody(req);
      const payload = JSON.parse(raw) as {
        channelId: string;
        subject: string;
        phones: string[];
      };
      const { channelId } = payload;
      const subject = (payload.subject ?? '').trim().slice(0, 100) || 'Silicon Support';
      const phones = Array.isArray(payload.phones) ? payload.phones : [];

      if (!channelId) return json(res, 400, { error: 'channelId required' });
      if (phones.length < 1) return json(res, 400, { error: 'At least one participant phone is required' });

      const session = sessions.get(channelId);
      if (!session) return json(res, 404, { error: 'No active session for this channel' });

      const participants = phones.map(p => {
        const digits = String(p).replace(/[^\d]/g, '');
        return `${digits}@s.whatsapp.net`;
      });

      const meta = await session.socket.groupCreate(subject, participants);
      const groupJid = meta.id;
      console.log(`[Worker] Created group ${groupJid} (${subject}) via channel ${channelId}`);
      return json(res, 200, { ok: true, groupJid, subject });
    } catch (err) {
      console.error('[Worker] Failed to create group:', err);
      return json(res, 500, { error: String(err) });
    }
  }

  // POST /group/metadata — fetch live participants
  if (method === 'POST' && url === '/group/metadata') {
    try {
      const raw = await readBody(req);
      const { channelId, groupJid } = JSON.parse(raw) as { channelId: string; groupJid: string };
      if (!channelId || !groupJid) return json(res, 400, { error: 'channelId and groupJid required' });
      const session = sessions.get(channelId);
      if (!session) return json(res, 404, { error: 'No active session for this channel' });

      const meta = await session.socket.groupMetadata(groupJid);
      const phones: string[] = [];
      for (const p of meta.participants ?? []) {
        const id = typeof p === 'string' ? p : (p as { id?: string }).id;
        if (!id || typeof id !== 'string') continue;
        if (id.endsWith('@s.whatsapp.net')) {
          const digits = id.split('@')[0].split(':')[0].replace(/\D/g, '');
          if (digits) phones.push(digits);
        }
      }
      return json(res, 200, {
        ok: true,
        groupJid: meta.id,
        subject: meta.subject ?? '',
        phones,
        participantCount: (meta.participants ?? []).length,
      });
    } catch (err) {
      console.error('[Worker] Failed to fetch group metadata:', err);
      return json(res, 500, { error: String(err) });
    }
  }

  // POST /group/participants — add or remove phones
  if (method === 'POST' && url === '/group/participants') {
    try {
      const raw = await readBody(req);
      const payload = JSON.parse(raw) as {
        channelId: string;
        groupJid: string;
        action: 'add' | 'remove';
        phones: string[];
      };
      const { channelId, groupJid, action } = payload;
      const phones = Array.isArray(payload.phones) ? payload.phones : [];
      if (!channelId || !groupJid) return json(res, 400, { error: 'channelId and groupJid required' });
      if (action !== 'add' && action !== 'remove') return json(res, 400, { error: 'action must be add or remove' });
      if (phones.length < 1) return json(res, 400, { error: 'phones required' });

      const session = sessions.get(channelId);
      if (!session) return json(res, 404, { error: 'No active session for this channel' });

      const jids = phones.map(p => `${String(p).replace(/[^\d]/g, '')}@s.whatsapp.net`);
      await session.socket.groupParticipantsUpdate(groupJid, jids, action);
      console.log(`[Worker] groupParticipantsUpdate ${action} on ${groupJid}: ${jids.join(',')}`);
      return json(res, 200, { ok: true });
    } catch (err) {
      console.error('[Worker] Failed to update group participants:', err);
      return json(res, 500, { error: String(err) });
    }
  }

  // DELETE /sessions/:id  — terminate a session
  const deleteMatch = url.match(/^\/sessions\/(.+)$/);
  if (method === 'DELETE' && deleteMatch) {
    const channelId = deleteMatch[1];
    const session   = sessions.get(channelId);
    if (session) {
      try { await session.socket.logout(); } catch { /* ignore */ }
      sessions.delete(channelId);
    }
    return json(res, 204, null);
  }

  json(res, 404, { error: 'Not found' });
});

async function notifyApiReady(): Promise<void> {
  // Tell the API the worker is up so it can restore sessions
  try {
    await fetch(`${API_URL}/internal/worker-ready`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ready: true }),
    });
    console.log('[Worker] Notified API of readiness');
  } catch (err) {
    console.warn('[Worker] Could not notify API of readiness:', err);
  }
}

async function main() {
  console.log('[Worker] WhatsApp worker starting…');
  await waitForApi();

  server.listen(PORT, '0.0.0.0', async () => {
    console.log(`[Worker] HTTP API listening on port ${PORT}`);
    // Small delay to ensure the HTTP server is fully accepting connections
    setTimeout(notifyApiReady, 1000);
  });
}

main().catch(err => {
  console.error('[Worker] Fatal error:', err);
  process.exit(1);
});
