/**
 * Azure Speech Services transcription provider.
 *
 * Uses the Speech to text REST API for short audio (≤60s).
 * Endpoint: https://<REGION>.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1
 * Auth: Ocp-Apim-Subscription-Key header.
 *
 * Requires AZURE_SPEECH_KEY + AZURE_SPEECH_REGION env vars.
 */

import type { TranscriptionProvider, TranscriptionInput, TranscriptionResult } from '../types.js';

export interface AzureSTTConfig {
  speechKey: string;
  speechRegion: string;
}

/** Map filename extension to Azure-compatible Content-Type. */
function audioContentType(filename: string): string {
  const ext = filename.split('.').pop()?.toLowerCase();
  switch (ext) {
    case 'wav':
      return 'audio/wav';
    case 'ogg':
      return 'audio/ogg; codecs=opus';
    case 'mp3':
    case 'mpeg':
    case 'mpga':
      return 'audio/mpeg';
    case 'webm':
      return 'audio/webm; codecs=opus';
    case 'flac':
      return 'audio/flac';
    case 'm4a':
    case 'mp4':
      return 'audio/mp4';
    default:
      return 'audio/wav';
  }
}

/**
 * Map ISO 639-1 two-letter code to Azure BCP-47 locale.
 * Azure requires full locale (e.g. "en-US"), not just "en".
 * Falls back to `${code}-${CODE}` only for unmapped languages.
 */
const ISO_TO_LOCALE: Record<string, string> = {
  en: 'en-US',
  es: 'es-ES',
  fr: 'fr-FR',
  de: 'de-DE',
  it: 'it-IT',
  pt: 'pt-BR',
  ja: 'ja-JP',
  ko: 'ko-KR',
  zh: 'zh-CN',
  ar: 'ar-SA',
  hi: 'hi-IN',
  ru: 'ru-RU',
  nl: 'nl-NL',
  sv: 'sv-SE',
  pl: 'pl-PL',
  tr: 'tr-TR',
  vi: 'vi-VN',
  th: 'th-TH',
  id: 'id-ID',
  uk: 'uk-UA',
  cs: 'cs-CZ',
  da: 'da-DK',
  fi: 'fi-FI',
  el: 'el-GR',
  he: 'he-IL',
  hu: 'hu-HU',
  nb: 'nb-NO',
  ro: 'ro-RO',
  sk: 'sk-SK',
  ms: 'ms-MY',
};

export function toAzureLocale(language: string): string {
  // Already a full BCP-47 locale (e.g. "en-US", "pt-BR")
  if (language.length > 3) return language;
  const lower = language.toLowerCase();
  return ISO_TO_LOCALE[lower] ?? `${lower}-${lower.toUpperCase()}`;
}

export class AzureTranscriptionProvider implements TranscriptionProvider {
  private config: AzureSTTConfig;

  constructor(config?: Partial<AzureSTTConfig>) {
    this.config = {
      speechKey: config?.speechKey ?? process.env.AZURE_SPEECH_KEY ?? '',
      speechRegion: config?.speechRegion ?? process.env.AZURE_SPEECH_REGION ?? '',
    };
  }

  isConfigured(): boolean {
    return Boolean(this.config.speechKey && this.config.speechRegion);
  }

  async transcribe(input: TranscriptionInput): Promise<TranscriptionResult> {
    if (!this.isConfigured()) {
      throw new Error(
        'Azure Speech is not configured (missing AZURE_SPEECH_KEY or AZURE_SPEECH_REGION)'
      );
    }

    const { speechKey, speechRegion } = this.config;
    const locale = toAzureLocale(input.language || 'en');

    const url = new URL(
      `https://${speechRegion}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1`
    );
    url.searchParams.set('language', locale);
    url.searchParams.set('format', 'detailed');

    const contentType = audioContentType(input.filename);

    const response = await fetch(url.toString(), {
      method: 'POST',
      headers: {
        'Ocp-Apim-Subscription-Key': speechKey,
        'Content-Type': contentType,
        Accept: 'application/json',
      },
      body: input.audio,
      signal: AbortSignal.timeout(120_000),
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Azure Speech error ${response.status}: ${errorText}`);
    }

    const data = (await response.json()) as {
      RecognitionStatus: string;
      DisplayText?: string;
      Duration?: number;
      NBest?: Array<{
        Display: string;
        Confidence: number;
      }>;
    };

    if (data.RecognitionStatus !== 'Success') {
      throw new Error(`Azure Speech recognition failed: ${data.RecognitionStatus}`);
    }

    // Duration is in 100-nanosecond ticks
    const durationSeconds = data.Duration ? data.Duration / 10_000_000 : null;

    // Use NBest[0] if available (higher quality), fall back to DisplayText
    const text = data.NBest?.[0]?.Display ?? data.DisplayText ?? '';

    return {
      text,
      language: locale,
      durationSeconds,
      model: `azure-speech-${speechRegion}`,
    };
  }
}
