import type {
  DialogueAPI,
  LatestSessionResponse,
  SSEEvent,
  SessionConfig,
  SessionInfo,
  GreetingInfo,
  TaskHint,
  DialogueReport,
  SentenceEvaluation,
} from '@/types/englishSpeaking'
import { getSpeakingApiBaseUrl } from './speakingApiConfig'

const API_BASE = getSpeakingApiBaseUrl()

export class DialogueApiError extends Error {
  status: number
  constructor(message: string, status: number) {
    super(message)
    this.status = status
    this.name = 'DialogueApiError'
  }
}

// ==================== SSE 解析 ====================

async function* parseSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>): AsyncGenerator<SSEEvent> {
  const decoder = new TextDecoder()
  let buffer = ''

  try {
    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''

      let eventType = ''
      let data = ''

      for (const line of lines) {
        if (line.startsWith('event:')) {
          eventType = line.slice(6).trim()
        } else if (line.startsWith('data:')) {
          data = line.slice(5).trim()
        } else if (line === '' && eventType && data) {
          try {
            const parsed = JSON.parse(data)
            if (eventType === 'transcript') {
              yield { type: 'transcript', text: parsed.text }
            } else if (eventType === 'token') {
              yield { type: 'token', text: parsed.content ?? parsed.text }
            } else if (eventType === 'done') {
              yield { type: 'done', isComplete: parsed.isComplete }
            }
          } catch {
            // skip malformed JSON
          }
          eventType = ''
          data = ''
        }
      }
    }
  } finally {
    reader.releaseLock()
  }
}

// ==================== Backend shape types ====================

interface BackendEvaluation {
  status: 'pending' | 'completed' | 'failed'
  accuracyScore: number | null
  fluencyScore: number | null
  completenessScore: number | null
  prosodyScore: number | null
  wordAnalysis: unknown
  contentFeedback: {
    comment: string
    betterExpression: string
  } | null
}

interface BackendRound {
  round: number
  role: 'ai' | 'student'
  content: string
  audioUrl: string | null
  evaluation?: BackendEvaluation
}

interface BackendReportResponse {
  sessionId: string
  topic: string
  status: 'evaluating' | 'ready' | 'failed' | 'incomplete'
  rounds: BackendRound[]
  overall: BackendOverall | null
  summary: string | null
}

interface BackendOverall {
  aiComment: string
  highlights: string[]
  improvements: string[]
}

function hasCompleteScores(evaluation?: BackendEvaluation): evaluation is BackendEvaluation & {
  accuracyScore: number
  fluencyScore: number
  completenessScore: number
  prosodyScore: number
} {
  return !!evaluation
    && evaluation.status === 'completed'
    && typeof evaluation.accuracyScore === 'number'
    && typeof evaluation.fluencyScore === 'number'
    && typeof evaluation.completenessScore === 'number'
    && typeof evaluation.prosodyScore === 'number'
}

function adaptReport(raw: BackendReportResponse): DialogueReport {
  const sentenceEvaluations: SentenceEvaluation[] = raw.rounds.map((r, idx) => {
    const pronunciation = r.role === 'student' && hasCompleteScores(r.evaluation)
      ? {
          accuracy: r.evaluation.accuracyScore,
          fluency: r.evaluation.fluencyScore,
          intonation: r.evaluation.prosodyScore,
          stress: r.evaluation.completenessScore,
        }
      : undefined

    return {
      id: `${raw.sessionId}-${idx}`,
      round: r.round,
      role: r.role,
      content: r.content,
      audioUrl: r.audioUrl ?? undefined,
      score: pronunciation
        ? Math.round((pronunciation.accuracy + pronunciation.fluency + pronunciation.intonation + pronunciation.stress) / 4)
        : undefined,
      pronunciation,
      feedback: r.evaluation?.contentFeedback ?? undefined,
    }
  })

  const studentEvals = sentenceEvaluations.filter(s => s.role === 'student' && s.pronunciation)
  const avg = studentEvals.length > 0
    ? Math.round(
        studentEvals.reduce(
          (sum, s) => sum + (s.pronunciation!.accuracy + s.pronunciation!.fluency + s.pronunciation!.intonation + s.pronunciation!.stress) / 4,
          0,
        ) / studentEvals.length,
      )
    : 0
  const avgDim = (key: 'accuracy' | 'fluency' | 'intonation' | 'stress') => {
    if (studentEvals.length === 0) return 0
    return Math.round(studentEvals.reduce((sum, s) => sum + (s.pronunciation?.[key] ?? 0), 0) / studentEvals.length)
  }
  const overall = raw.overall
  const highest = studentEvals.reduce<SentenceEvaluation | null>(
    (best, s) => (!best || (s.score ?? 0) > (best.score ?? 0) ? s : best),
    null,
  )

  return {
    status: raw.status,
    evaluation: {
      overallScore: avg,
      scoreLevel: avg >= 85 ? 'excellent' : avg >= 70 ? 'good' : avg >= 60 ? 'fair' : 'needsWork',
      percentile: 0,
      dimensions: {
        fluency: avgDim('fluency'),
        interaction: avgDim('intonation'),
        vocabulary: avgDim('stress'),
        grammar: avgDim('accuracy'),
      },
      aiComment: overall?.aiComment ?? raw.summary ?? '',
      highlights: overall?.highlights ?? [],
      improvements: overall?.improvements ?? [],
      nextChallenge: {},
      statistics: {
        totalRounds: sentenceEvaluations.length ? Math.max(...sentenceEvaluations.map(s => s.round)) : 0,
        averageScore: avg,
        highestScore: highest?.score ?? 0,
        highestRound: highest?.round ?? 0,
        grammarErrors: 0,
        excellentExpressions: 0,
        totalDuration: 0,
      },
      sentenceEvaluations,
    },
  }
}

// ==================== Real API ====================

export class RealDialogueAPI implements DialogueAPI {
  async createSession(config: SessionConfig): Promise<SessionInfo> {
    const res = await fetch(`${API_BASE}/session`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({
        topic: config.topic,
        grade: config.grade,
        vocabulary: config.vocabulary ?? [],
        sentences: config.sentences ?? [],
        totalRounds: config.totalRounds,
        durationMinutes: config.durationMinutes,
        roleId: config.roleId,
        configId: config.configId ?? null,
        userId: config.userId ?? null,
      }),
    })
    if (!res.ok) {
      throw new DialogueApiError(`createSession failed: ${res.status}`, res.status)
    }
    const body = await res.json()
    return {
      sessionId: body.sessionId,
      totalRounds: body.totalRounds,
      currentRound: body.currentRound,
      expiresAt: body.expiresAt ?? null,
    }
  }

  async getLatestSession(configId: string, userId: string): Promise<LatestSessionResponse> {
    const params = new URLSearchParams({ configId, userId })
    const res = await fetch(`${API_BASE}/sessions/latest?${params.toString()}`, {
      method: 'GET',
      credentials: 'include',
    })
    if (!res.ok) {
      throw new DialogueApiError(`getLatestSession failed: ${res.status}`, res.status)
    }
    return res.json()
  }

  async completeSession(sessionId: string): Promise<void> {
    const res = await fetch(`${API_BASE}/session/${encodeURIComponent(sessionId)}/complete`, {
      method: 'POST',
      credentials: 'include',
    })
    if (!res.ok) {
      throw new DialogueApiError(`completeSession failed: ${res.status}`, res.status)
    }
  }

  async generateGreeting(sessionId: string, turnId: string, signal?: AbortSignal): Promise<GreetingInfo> {
    const res = await fetch(`${API_BASE}/session/${sessionId}/greeting`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      signal,
      body: JSON.stringify({ turnId }),
    })
    if (!res.ok) {
      const text = await res.text().catch(() => '')
      throw new DialogueApiError(
        `greeting failed: ${res.status}${text ? ` (${text.slice(0, 100)})` : ''}`,
        res.status,
      )
    }
    const body = await res.json()
    return { aiMessage: body.aiMessage }
  }

  async generateTaskHint(sessionId: string): Promise<TaskHint> {
    const res = await fetch(`${API_BASE}/session/${sessionId}/task-hint`, {
      method: 'POST',
      credentials: 'include',
    })
    if (!res.ok) {
      throw new DialogueApiError(`task hint failed: ${res.status}`, res.status)
    }
    return await res.json()
  }

  async *speak(sessionId: string, audioBlob: Blob, signal: AbortSignal, turnId: string): AsyncGenerator<SSEEvent> {
    const formData = new FormData()
    formData.append('sessionId', sessionId)
    formData.append('audio', audioBlob, 'recording.webm')
    formData.append('turnId', turnId)

    const res = await fetch(`${API_BASE}/speak`, {
      method: 'POST',
      credentials: 'include',
      body: formData,
      signal,
    })
    if (!res.ok) throw new Error(`speak failed: ${res.status}`)
    if (!res.body) throw new Error('No response body')

    yield* parseSSEStream(res.body.getReader())
  }

  async getReport(sessionId: string): Promise<DialogueReport> {
    const res = await fetch(`${API_BASE}/report?sessionId=${encodeURIComponent(sessionId)}`, {
      credentials: 'include',
    })
    if (!res.ok) throw new Error(`getReport failed: ${res.status}`)
    const raw: BackendReportResponse = await res.json()
    return adaptReport(raw)
  }
}

export function createDialogueApi(): DialogueAPI {
  return new RealDialogueAPI()
}
