import { spawn, ChildProcess } from 'child_process';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { randomUUID } from 'crypto';

const __filename = fileURLToPath(import.meta.url);
const __dirname = join(__filename, '..');

const SCRIPT_DIR = join(__dirname, '..');

const PYTHON = join(SCRIPT_DIR, '..', '.venv', 'bin', 'python3');

const whisperModel = process.env.WHISPER_MODEL ?? 'base';
const whisperLanguage = process.env.WHISPER_LANGUAGE ?? 'ca';

interface TranscriptResult {
  msgId: string;
  text?: string;
  error?: string;
}

interface InitResult {
  type: 'init_ok' | 'init_error';
}

class WhisperService {
  private proc: ChildProcess | null = null;
  private onInitResolve: (() => void) | null = null;
  private onInitReject: ((err: Error) => void) | null = null;

  spawn(): void {
    if (this.proc) return;

    const scriptPath = join(SCRIPT_DIR, 'whisper-worker.py');

    this.proc = spawn(PYTHON, [scriptPath], {
      stdio: ['pipe', 'pipe', 'pipe'],
      env: { ...process.env },
    });

    if (!this.proc.stdout || !this.proc.stderr || !this.proc.stdin) {
      console.error('[whisper-svc] Missing stdin/stdout/stderr');
      this.proc = null;
      return;
    }

    const proc = this.proc;
    if (!proc?.stdout) return;
    let buf = '';
    proc.stdout.on('data', (chunk: Buffer) => {
      buf += chunk.toString();
      while (true) {
        const nl = buf.indexOf('\n');
        if (nl === -1) break;
        const line = buf.slice(0, nl).trim();
        buf = buf.slice(nl + 1);
        if (!line) continue;
        try {
          const msg = JSON.parse(line);
          if (msg.type === 'ready') {
            console.log('[whisper-svc] Worker ready, sending init...');
            proc.stdin!.write(
              JSON.stringify({ type: 'init', model: whisperModel, language: whisperLanguage }) + '\n',
            );
          } else if (msg.type === 'init_ok') {
            console.log(`[whisper-svc] Model loaded (model=${whisperModel}, lang=${whisperLanguage})`);
            if (this.onInitResolve) {
              const r = this.onInitResolve;
              this.onInitResolve = null;
              this.onInitReject = null;
              r();
            }
          } else if (msg.type === 'init_error') {
            const err = new Error(`whisper-svc init failed: ${msg.error || 'unknown'}`);
            if (this.onInitReject) {
              const r = this.onInitReject;
              this.onInitResolve = null;
              this.onInitReject = null;
              r(err);
            }
          } else if (msg.type === 'transcript' || msg.type === 'error') {
            this.resolveTranscript(msg.msgId, msg);
          }
        } catch { /* skip */ }
      }
    });

    const stderr = proc.stderr;
    if (stderr) {
      stderr.on('data', (chunk: Buffer) => {
        const text = chunk.toString().trim();
        if (text) console.log(`[whisper-svc] stderr: ${text}`);
      });
    }

    proc.on('exit', (code, signal) => {
      console.log(`[whisper-svc] Exited code=${code} signal=${signal}`);
      this.proc = null;
    });

    proc.on('error', (err) => {
      console.error(`[whisper-svc] Error: ${err.message}`);
      this.proc = null;
    });
  }

  private pending: Map<string, (result: TranscriptResult) => void> = new Map();

  private resolveTranscript(msgId: string, msg: { type?: string; text?: string; error?: string }) {
    const cb = this.pending.get(msgId);
    this.pending.delete(msgId);
    if (cb) {
      if (msg.type === 'error') {
        cb({
          msgId,
          text: msg.text,
          error: msg.error ?? msg.text ?? 'unknown error',
        });
      } else {
        cb({ msgId, text: msg.text });
      }
    }
  }

  private waitForInit(): Promise<void> {
    if (this.onInitResolve) return Promise.resolve(); // already initializing

    return new Promise<void>((resolve, reject) => {
      let cleared = false;
      const timer = setTimeout(() => {
        if (cleared) return;
        cleared = true;
        this.onInitReject = null;
        reject(new Error('whisper-svc init timed out'));
      }, 90_000);
      this.onInitResolve = () => {
        if (cleared) return;
        cleared = true;
        clearTimeout(timer);
        resolve();
      };
      this.onInitReject = (err: Error) => {
        if (cleared) return;
        cleared = true;
        clearTimeout(timer);
        reject(err);
      };
    });
  }

  async transcribe(audioPath: string): Promise<string> {
    if (!this.proc) {
      this.spawn();
    }

    // await this.waitForInit();

    const msgId = randomUUID() + '-' + Date.now();

    return new Promise((resolve, reject) => {
      let cleared = false;
      let timer: ReturnType<typeof setTimeout> | null = null;

      const resolvePromise = (result: TranscriptResult) => {
        if (cleared) return;
        cleared = true;
        if (timer) clearTimeout(timer);
        if (result.error) {
          reject(new Error(`whisper-svc: ${result.error}`));
        } else if (result.text) {
          resolve(result.text.trim());
        } else {
          reject(new Error('whisper-svc: empty response'));
        }
      };

      this.pending.set(msgId, resolvePromise);

      timer = setTimeout(() => {
        if (cleared) return;
        cleared = true;
        this.pending.delete(msgId);
        reject(new Error('whisper-svc: transcription timed out'));
      }, 120_000);

      const proc = this.proc;
      if (proc && proc.stdin) {
        proc.stdin.write(
          JSON.stringify({ type: 'transcribe', path: audioPath, msgId }) + '\n',
        );
      } else {
        cleared = true;
        if (timer) clearTimeout(timer);
        this.pending.delete(msgId);
        reject(new Error('whisper subprocess not running'));
      }
    });
  }

  async shutdown(): Promise<void> {
    const proc = this.proc;
    if (proc) {
      try {
        proc.stdin!.end();
        await new Promise<void>((resolve) => {
          proc.on('exit', () => resolve());
          setTimeout(() => {
            if (!proc.killed) proc.kill('SIGTERM');
            resolve();
          }, 3000);
        });
      } catch { /* ignore */ }
      this.proc = null;
    }
  }
}

export const whisperService = new WhisperService();
