import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import { getDb, nowIso, newId } from './db.js';
import { readFileForEngine, uploadFile, getDiskFile, getRootFolder, createFolder } from './disk.js';
import { getSecret } from './secrets.js';
import { wrapFetch } from './egress.js';

function ensureTranscriptsFolder(projectId) {
  const db = getDb();
  const root = getRootFolder(projectId);
  const existing = db.prepare('SELECT * FROM disk_folders WHERE project_id = ? AND parent_id = ? AND name = ? LIMIT 1')
    .get(projectId, root.id, 'transcripts');
  if (existing) return existing;
  return createFolder(projectId, root.id, 'transcripts');
}

async function openAiWhisper(source, opts = {}) {
  const apiKey = getSecret('OPENAI_API_KEY', opts) || process.env.OPENAI_API_KEY;
  if (!apiKey) throw new Error('OPENAI_API_KEY missing for OpenAI Whisper transcription');
  const buffer = fs.readFileSync(source.storage_path);
  const form = new FormData();
  form.set('model', opts.model || 'whisper-1');
  form.set('file', new Blob([buffer], { type: source.mime_type || 'application/octet-stream' }), source.name);
  if (opts.language) form.set('language', opts.language);
  const loggedFetch = wrapFetch('transcribe_audio', opts.agent_id ?? null, opts.fetch || fetch);
  const res = await loggedFetch('https://api.openai.com/v1/audio/transcriptions', {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}` },
    body: form,
  });
  if (!res.ok) throw new Error(`openai whisper failed: ${res.status}`);
  const data = await res.json();
  return {
    text: data.text || '',
    language: data.language || opts.language || null,
    duration_seconds: data.duration_seconds ?? data.duration ?? null,
  };
}

async function elevenLabsScribe(source, opts = {}) {
  const apiKey = getSecret('ELEVENLABS_API_KEY', opts) || process.env.ELEVENLABS_API_KEY;
  if (!apiKey) throw new Error('ELEVENLABS_API_KEY missing for ElevenLabs Scribe transcription');
  const buffer = fs.readFileSync(source.storage_path);
  const form = new FormData();
  form.set('model_id', opts.model || 'scribe_v1');
  form.set('file', new Blob([buffer], { type: source.mime_type || 'application/octet-stream' }), source.name);
  if (opts.language) form.set('language_code', opts.language);
  const loggedFetch = wrapFetch('transcribe_audio', opts.agent_id ?? null, opts.fetch || fetch);
  const res = await loggedFetch('https://api.elevenlabs.io/v1/speech-to-text', {
    method: 'POST',
    headers: { 'xi-api-key': apiKey },
    body: form,
  });
  if (!res.ok) {
    let detail = '';
    try { detail = (await res.text()).slice(0, 256); } catch {}
    throw new Error(`elevenlabs scribe failed: ${res.status} ${detail.replace(apiKey, '***')}`);
  }
  const data = await res.json();
  // Scribe returns { text, language_code, language_probability, words: [...] }
  return {
    text: data.text || '',
    language: data.language_code || opts.language || null,
    duration_seconds: data.duration_seconds ?? null,
  };
}

function localWhisper(source, opts = {}) {
  if (opts.mock_text) return { text: opts.mock_text, language: opts.language || null, duration_seconds: opts.duration_seconds || null };
  const command = opts.whisper_command || process.env.WHISPER_CPP_BIN || process.env.LOCAL_WHISPER_BIN;
  if (!command) return { text: `Transcript for ${source.name}`, language: opts.language || null, duration_seconds: null };
  const out = spawnSync(command, ['-f', source.storage_path], { encoding: 'utf8', timeout: opts.timeout_ms || 120000 });
  if (out.status !== 0) throw new Error(`local whisper failed: ${out.stderr || out.stdout || out.status}`);
  return { text: String(out.stdout || '').trim(), language: opts.language || null, duration_seconds: null };
}

function resolveProvider(opts = {}) {
  if (opts.use_local || process.env.LOCAL_WHISPER === '1') return 'local';
  const requested = String(opts.provider || process.env.TRANSCRIBE_DEFAULT_PROVIDER || 'openai').toLowerCase();
  if (['openai', 'whisper'].includes(requested)) return 'openai';
  if (['elevenlabs', 'scribe', 'elevenlabs_scribe'].includes(requested)) {
    const hasKey = (getSecret('ELEVENLABS_API_KEY', opts) || process.env.ELEVENLABS_API_KEY);
    if (!hasKey) {
      console.warn('[transcribe] ELEVENLABS_API_KEY missing — falling back to openai whisper');
      return 'openai';
    }
    return 'elevenlabs';
  }
  if (requested === 'local') return 'local';
  return 'openai';
}

const PROVIDER_TO_ENGINE = {
  openai: 'openai_whisper',
  elevenlabs: 'elevenlabs_scribe',
  local: 'local_whisper',
};

export async function transcribeFile(fileId, opts = {}) {
  const source = getDiskFile(fileId);
  if (!source) throw new Error('source file not found');
  const provider = resolveProvider(opts);
  const useLocal = provider === 'local';
  const engine = PROVIDER_TO_ENGINE[provider];
  if (source.local_only && !useLocal) return { status: 'allowlist_violation', error: `local_only audio cannot be sent to ${provider}` };
  const permitted = readFileForEngine(fileId, engine);
  if (permitted.redacted) return { status: 'allowlist_violation', error: 'audio source redacted for engine' };

  const db = getDb();
  const now = nowIso();
  const id = newId('tr');
  db.prepare(`INSERT INTO transcripts (id, source_file_id, project_id, status, provider, engine, text, created_at, updated_at)
    VALUES (?, ?, ?, 'pending', ?, ?, '', ?, ?)`).run(id, fileId, source.project_id, provider, engine, now, now);

  const dispatch = provider === 'local'
    ? () => localWhisper(source, opts)
    : provider === 'elevenlabs'
      ? () => elevenLabsScribe(source, opts)
      : () => openAiWhisper(source, opts);

  try {
    db.prepare("UPDATE transcripts SET status='running', updated_at=? WHERE id=?").run(nowIso(), id);
    const result = await dispatch();
    const text = result.text || `Transcript for ${source.name}`;
    const transcriptsFolder = ensureTranscriptsFolder(source.project_id);
    const artifact = uploadFile(source.project_id, transcriptsFolder.id, {
      name: `${source.name}.transcript.md`,
      content: `# Transcript: ${source.name}\n\n${text}\n`,
      type: 'text/markdown',
    }, { local_only: source.local_only });
    const completedAt = nowIso();
    db.prepare(`UPDATE transcripts
      SET status='complete', text=?, artifact_file_id=?, engine=?, language=?, duration_seconds=?, completed_at=?, updated_at=?
      WHERE id=?`).run(text, artifact.id, engine, result.language || null, result.duration_seconds || null, completedAt, completedAt, id);
    const row = db.prepare('SELECT * FROM transcripts WHERE id = ?').get(id);
    return { ...row, transcript_id: row.id };
  } catch (error) {
    db.prepare("UPDATE transcripts SET status='error', error=?, updated_at=? WHERE id=?").run(String(error?.message || error), nowIso(), id);
    throw error;
  }
}

export function listTranscripts(opts = {}) {
  const where = [];
  const params = [];
  if (opts.project_id) { where.push('project_id=?'); params.push(opts.project_id); }
  if (opts.source_file_id) { where.push('source_file_id=?'); params.push(opts.source_file_id); }
  return getDb().prepare(`SELECT * FROM transcripts ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY created_at DESC`).all(...params);
}
