import { Injectable, Logger } from '@nestjs/common'
import { execSync } from 'child_process'
import * as path from 'path'
import * as fs from 'fs'

export interface AsrSegment {
  startTime: number
  endTime: number
  speaker: 'interviewer' | 'candidate'
  text: string
}

export interface AsrResult {
  fullText: string
  segments: AsrSegment[]
  duration: number
}

export interface AsrConfig {
  /** Path to whisper.cpp build/bin/ directory */
  whisperCppPath?: string
  /** Model name: tiny | base | small | medium */
  model?: string
  /** Language code (zh, en, auto) */
  language?: string
}

@Injectable()
export class AsrService {
  private readonly logger = new Logger(AsrService.name)

  private readonly whisperCppPath: string
  private readonly modelPath: string
  private readonly language: string
  private readonly cliPath: string

  constructor() {
    // Configuration via env vars with sensible defaults
    this.whisperCppPath = process.env.WHISPER_CPP_PATH || '/home/wlt/whisper.cpp'
    this.language = process.env.WHISPER_LANGUAGE || 'zh'

    const modelName = process.env.WHISPER_MODEL || 'base'
    this.modelPath = path.join(this.whisperCppPath, 'models', `ggml-${modelName}.bin`)
    this.cliPath = path.join(this.whisperCppPath, 'build', 'bin', 'whisper-cli')

    // Validate whisper.cpp installation on startup
    if (!fs.existsSync(this.cliPath)) {
      this.logger.warn(`whisper-cli not found at ${this.cliPath}. ASR will fall back to mock.`)
    }
    if (!fs.existsSync(this.modelPath)) {
      this.logger.warn(`Whisper model not found at ${this.modelPath}. ASR will fall back to mock.`)
    }
  }

  async transcribe(audioPath: string, _mimeType: string): Promise<AsrResult> {
    this.logger.log(`Transcribing audio: ${audioPath}`)

    // Try companion .txt file first (for debugging/testing)
    const txtPath = audioPath.replace(/\.(mp3|m4a|wav|aac|ogg|mp4|webm)$/i, '.txt')
    try {
      if (fs.existsSync(txtPath)) {
        const text = fs.readFileSync(txtPath, 'utf-8')
        this.logger.log(`Found companion transcript: ${txtPath}`)
        return {
          fullText: text,
          segments: [{
            startTime: 0,
            endTime: Math.max(text.length / 3.5, 10),
            speaker: 'candidate',
            text,
          }],
          duration: Math.max(text.length / 3.5, 10),
        }
      }
    } catch { /* ignore */ }

    // Try whisper.cpp
    if (fs.existsSync(this.cliPath) && fs.existsSync(this.modelPath)) {
      try {
        return await this.transcribeWithWhisper(audioPath)
      } catch (err: any) {
        this.logger.error(`whisper.cpp transcription failed: ${err.message}, falling back to mock`)
      }
    }

    // Fallback to mock
    this.logger.warn('Using MOCK ASR — whisper.cpp not available')
    return this.mockTranscribe()
  }

  private async transcribeWithWhisper(audioPath: string): Promise<AsrResult> {
    // Ensure audio file exists
    if (!fs.existsSync(audioPath)) {
      throw new Error(`Audio file not found: ${audioPath}`)
    }

    // Convert to WAV if needed (whisper.cpp works best with WAV)
    const wavPath = await this.ensureWav(audioPath)

    // Run whisper-cli with JSON output
    const cmd = [
      this.cliPath,
      '-m', this.modelPath,
      '-f', wavPath,
      '-l', this.language,
      '-oj',                       // JSON output
      '-t', String(Math.max(1, this.getCpuThreads())),  // thread count
      '--no-prints',              // suppress timing info on stderr
    ].join(' ')

    this.logger.log(`Running: ${this.cliPath} -m ${this.modelPath} -f ${wavPath} -l ${this.language}`)

    const stdout = execSync(cmd, { timeout: 600000, encoding: 'utf-8' }) // 10 min timeout

    // Parse the JSON output
    const segments = this.parseWhisperOutput(stdout)
    const fullText = segments.map(s => s.text).join(' ')
    const duration = segments.length > 0
      ? segments[segments.length - 1].endTime
      : 0

    return { fullText, segments, duration }
  }

  private parseWhisperOutput(stdout: string): AsrSegment[] {
    try {
      // whisper.cpp -oj outputs one JSON object per line
      const lines = stdout.trim().split('\n')
      const segments: AsrSegment[] = []

      for (const line of lines) {
        try {
          const parsed = JSON.parse(line)
          if (parsed.text && parsed.offsets) {
            segments.push({
              startTime: parsed.offsets.from / 1000, // ms to seconds
              endTime: parsed.offsets.to / 1000,
              speaker: 'candidate',
              text: parsed.text.trim(),
            })
          } else if (parsed.text && parsed.start !== undefined) {
            // Alternative format
            segments.push({
              startTime: parsed.start,
              endTime: parsed.end || parsed.start + 2,
              speaker: 'candidate',
              text: parsed.text.trim(),
            })
          }
        } catch { /* skip unparseable lines */ }
      }

      if (segments.length > 0) return segments
    } catch { /* fall through */ }

    // Fallback: treat entire output as raw text
    this.logger.warn('Could not parse structured JSON output, using raw text')
    return [{
      startTime: 0,
      endTime: 0,
      speaker: 'candidate',
      text: stdout.trim(),
    }]
  }

  /**
   * Convert audio to WAV format if needed.
   * Uses ffmpeg if available, otherwise returns original path.
   */
  private async ensureWav(audioPath: string): Promise<string> {
    const ext = path.extname(audioPath).toLowerCase()
    if (ext === '.wav') return audioPath

    const wavPath = audioPath.replace(/\.[^.]+$/, '.wav')
    try {
      execSync(`ffmpeg -y -i "${audioPath}" -ar 16000 -ac 1 -c:a pcm_s16le "${wavPath}"`, {
        timeout: 300000,
        encoding: 'utf-8',
        stdio: 'pipe',
      })
      this.logger.log(`Converted ${audioPath} to WAV: ${wavPath}`)
      return wavPath
    } catch (err: any) {
      this.logger.warn(`ffmpeg conversion failed: ${err.message}. Trying original format.`)
      return audioPath
    }
  }

  private getCpuThreads(): number {
    try {
      return parseInt(process.env.WHISPER_THREADS || '', 10) ||
        require('os').cpus().length || 4
    } catch {
      return 4
    }
  }

  /** Mock transcription for development/testing when whisper.cpp is not available */
  private mockTranscribe(): AsrResult {
    const paragraphs = [
      '我毕业于计算机科学与技术专业，大学期间主要学习了数据结构、算法、操作系统、计算机网络等核心课程。',
      '在项目经验方面，我参与过一个电商平台的开发，主要负责后端接口的设计和实现，使用了 Node.js 和 MongoDB 技术栈。',
      '这个项目的难点在于高并发场景下的性能优化，我通过引入 Redis 缓存和数据库索引优化，将接口响应时间从 2 秒降低到了 200 毫秒。',
      '关于这个岗位，我了解到贵公司主要使用 React 技术栈，我之前在两个项目中使用过 React，对 Hooks、状态管理、组件化开发都比较熟悉。',
    ]
    const fullText = paragraphs.join('\n')
    return {
      fullText,
      segments: [{
        startTime: 0,
        endTime: 120,
        speaker: 'candidate',
        text: fullText,
      }],
      duration: 120,
    }
  }
}
