import { BrowserWindow, ipcMain, dialog as dialogBox, app } from 'electron'
import { captureScreen, closeScreenshot, getCapturedImage, getCapturedBase64, clearCapturedImage, startScreenshot } from './screenshot'
import { getMainWindow, openDialogWindow } from './window'
import * as fs from 'fs'
import * as path from 'path'
import log from 'electron-log'

let store: any = null
function getStore() {
  if (!store) {
    const Store = require('electron-store').default
    store = new Store({
      defaults: {
        apiKey: '',
        baseUrl: 'https://api.siliconflow.cn/v1',
        model: '',
        shortcut: 'Alt+Q',
        theme: 'light',
        fontSize: 14,
        proxy: '',
        timeout: 120,
        screenshotSavePath: ''
      }
    })
    // migrate old low timeout values
    const t = store.get('timeout') as number | undefined
    if (t !== undefined && t < 60) store.set('timeout', 120)
  }
  return store
}

let db: any = null
function getDb() {
  if (!db) {
    const Database = require('better-sqlite3')
    const dbPath = require('path').join(app.getPath('userData'), 'grading.db')
    db = new Database(dbPath)
    db.prepare(`CREATE TABLE IF NOT EXISTS history (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      question_id INTEGER,
      image_path TEXT,
      student_answer TEXT,
      ai_score REAL,
      teacher_score REAL,
      confidence REAL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      question_title TEXT DEFAULT '',
      max_score REAL DEFAULT 100,
      reference_answer TEXT DEFAULT '',
      rubric TEXT DEFAULT '',
      deductions TEXT DEFAULT '',
      comment TEXT DEFAULT '',
      result_json TEXT DEFAULT '',
      image_data TEXT DEFAULT ''
    )`).run()
    // migrate: add columns for existing DBs
    const migrateCols: { name: string; type: string }[] = [
      { name: 'question_title', type: 'TEXT DEFAULT \'\'' },
      { name: 'max_score', type: 'REAL DEFAULT 100' },
      { name: 'reference_answer', type: 'TEXT DEFAULT \'\'' },
      { name: 'rubric', type: 'TEXT DEFAULT \'\'' },
      { name: 'deductions', type: 'TEXT DEFAULT \'\'' },
      { name: 'comment', type: 'TEXT DEFAULT \'\'' },
      { name: 'result_json', type: 'TEXT DEFAULT \'\'' },
      { name: 'image_data', type: 'TEXT DEFAULT \'\'' },
      { name: 'reference_answer_image', type: 'TEXT DEFAULT \'\'' }
    ]
    for (const col of migrateCols) {
      try { db.prepare(`ALTER TABLE history ADD COLUMN ${col.name} ${col.type}`).run() } catch (_) {}
    }
    db.prepare(`CREATE TABLE IF NOT EXISTS exam (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT,
      course TEXT,
      questions_json TEXT DEFAULT '',
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )`).run()
    try { db.prepare(`ALTER TABLE exam ADD COLUMN questions_json TEXT DEFAULT ''`).run() } catch (_) {}
    db.prepare(`CREATE TABLE IF NOT EXISTS question (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      exam_id INTEGER,
      title TEXT,
      max_score INTEGER,
      rubric TEXT
    )`).run()
    db.prepare(`CREATE TABLE IF NOT EXISTS rubric (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      question_title TEXT NOT NULL DEFAULT '',
      max_score REAL NOT NULL DEFAULT 100,
      reference_answer TEXT NOT NULL DEFAULT '',
      rubric TEXT NOT NULL DEFAULT '',
      sort_order INTEGER NOT NULL DEFAULT 0,
      reference_answer_image TEXT NOT NULL DEFAULT ''
    )`).run()
    // migrate: add column for existing DBs
    try { db.prepare('ALTER TABLE rubric ADD COLUMN reference_answer_image TEXT NOT NULL DEFAULT \'\'').run() } catch (_) {}
  }
  return db
}

export function setupIpc(win: BrowserWindow): void {

  ipcMain.handle('window:dragMove', (_event, { deltaX, deltaY }: { deltaX: number; deltaY: number }) => {
    const [x, y] = win.getPosition()
    win.setPosition(x + deltaX, y + deltaY)
  })

  ipcMain.handle('window:dragEnd', (_event, { x, y }: { x: number; y: number }) => {
    win.setPosition(x, y)
  })

  ipcMain.handle('window:resizeMain', (_event, width: number) => {
    const [wx, wy] = win.getPosition()
    win.setBounds({ x: wx, y: wy, width: Math.max(200, Math.min(width, 1200)), height: 76 })
  })

  ipcMain.handle('screenshot:start', () => {
    log.info('IPC: screenshot:start')
    startScreenshot(win)
  })

  ipcMain.handle('screenshot:capture', async (_event, region: { x: number; y: number; width: number; height: number }) => {
    log.info('IPC: screenshot:capture', region)
    const base64 = await captureScreen(region)
    closeScreenshot()
    setTimeout(() => {
      const mainWin = getMainWindow()
      if (mainWin) {
        mainWin.show()
        mainWin.webContents.send('screenshot:completed', base64)
      }
      require('./shortcut').enableShortcut()
    }, 100)
    return base64
  })

  ipcMain.handle('screenshot:getLastImage', () => {
    return getCapturedBase64()
  })

  ipcMain.handle('screenshot:cancel', () => {
    log.info('IPC: screenshot:cancel')
    closeScreenshot()
    setTimeout(() => {
      const mainWin = getMainWindow()
      if (mainWin) mainWin.show()
      require('./shortcut').enableShortcut()
    }, 100)
  })

  ipcMain.handle('ai:grade', async (_event, { imageBase64, rubric, apiConfig, maxScore, referenceAnswer, questionTitle, referenceAnswerImage }: {
    imageBase64: string
    rubric: string
    apiConfig: { apiKey: string; baseUrl: string; model: string }
    maxScore?: number
    referenceAnswer?: string
    questionTitle?: string
    referenceAnswerImage?: string
  }) => {
    log.info('IPC: ai:grade')
    try {
      const result = await callAiApi(imageBase64, rubric, apiConfig, { maxScore, referenceAnswer, questionTitle, referenceAnswerImage })
      return result
    } catch (err: any) {
      log.error('AI grade failed:', err)
      const res = err?.response?.data
      const apiMsg = res?.error?.message || res?.error || res?.message || ''
      const allMsg = (apiMsg + ' ' + (err.message || '')).toLowerCase()
      if (allMsg.includes('does not support image') || allMsg.includes('image input') || allMsg.includes('not support image')) {
        throw new Error('当前模型不支持图片输入，请在设置中更换为支持多模态的视觉模型')
      }
      throw new Error(apiMsg || err.message || 'AI grading failed')
    }
  })

  ipcMain.handle('settings:get', () => {
    return getStore().store
  })

  ipcMain.handle('settings:set', (_event, settings: Record<string, unknown>) => {
    const s = getStore()
    for (const [key, value] of Object.entries(settings)) {
      s.set(key, value)
    }
    return true
  })

  ipcMain.handle('settings:selectDirectory', async () => {
    const result = await dialogBox.showOpenDialog({
      properties: ['openDirectory']
    })
    if (result.canceled || result.filePaths.length === 0) return null
    return result.filePaths[0]
  })

  ipcMain.handle('history:list', () => {
    try {
      return getDb().prepare('SELECT * FROM history ORDER BY created_at DESC LIMIT 100').all()
    } catch (err) {
      log.error('history:list error:', err)
      return []
    }
  })

  ipcMain.handle('history:delete', (_event, id: number) => {
    try {
      getDb().prepare('DELETE FROM history WHERE id = ?').run(id)
      return true
    } catch {
      return false
    }
  })

  ipcMain.handle('history:getById', (_event, id: number) => {
    try {
      const record = getDb().prepare('SELECT * FROM history WHERE id = ?').get(id) as any
      return record || null
    } catch {
      return null
    }
  })

  ipcMain.handle('grading:submit', (_event, data: {
    questionId: number
    imagePath: string
    aiScore: number
    teacherScore: number
    confidence: number
    studentAnswer?: string
    questionTitle?: string
    maxScore?: number
    referenceAnswer?: string
    rubric?: string
    deductions?: string[]
    comment?: string
    resultJson?: string
    imageData?: string
    referenceAnswerImage?: string
  }) => {
    log.info('grading:submit called', JSON.stringify({ ...data, imageData: data.imageData ? `len=${data.imageData.length}` : 'empty', referenceAnswerImage: data.referenceAnswerImage ? `len=${data.referenceAnswerImage.length}` : 'empty' }))
    const deductionsStr = JSON.stringify(data.deductions || [])
    getDb().prepare(`INSERT INTO history
      (question_id, image_path, student_answer, ai_score, teacher_score, confidence,
       question_title, max_score, reference_answer, rubric, deductions, comment, result_json, image_data,
       reference_answer_image)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
      data.questionId || 0,
      data.imagePath || '',
      data.studentAnswer || '',
      data.aiScore,
      data.teacherScore,
      data.confidence,
      data.questionTitle || '',
      data.maxScore ?? 100,
      data.referenceAnswer || '',
      data.rubric || '',
      deductionsStr,
      data.comment || '',
      data.resultJson || '',
      data.imageData || '',
      data.referenceAnswerImage || ''
    )
    return true
  })

  ipcMain.handle('template:list', () => {
    try {
      const db = getDb()
      const exams = db.prepare('SELECT * FROM exam ORDER BY created_at DESC').all() as any[]
      const result = exams.map((e: any) => {
        const questions = e.questions_json ? JSON.parse(e.questions_json) : []
        log.info('template:list item', { id: e.id, name: e.name, jsonLen: (e.questions_json || '').length, questionCount: questions.length })
        return { ...e, questions }
      })
      return result
    } catch {
      return []
    }
  })

  ipcMain.handle('template:exportJson', async () => {
    try {
      const d = getDb()
      const rows: any[] = d.prepare('SELECT * FROM exam ORDER BY created_at DESC').all()
      if (!rows.length) throw new Error('暂无模板可导出')
      const templates = rows.map(r => ({
        name: r.name,
        course: r.course,
        questions: r.questions_json ? JSON.parse(r.questions_json) : [],
        created_at: r.created_at
      }))
      const result = await dialogBox.showSaveDialog({
        defaultPath: '评分模板.json',
        filters: [{ name: 'JSON', extensions: ['json'] }]
      })
      if (result.canceled || !result.filePath) return false
      fs.writeFileSync(result.filePath, JSON.stringify(templates, null, 2), 'utf-8')
      return true
    } catch (err) {
      log.error('template:exportJson error:', err)
      return false
    }
  })

  ipcMain.handle('template:importJson', async () => {
    try {
      const result = await dialogBox.showOpenDialog({
        properties: ['openFile'],
        filters: [{ name: 'JSON', extensions: ['json'] }]
      })
      if (result.canceled || result.filePaths.length === 0) return null
      const content = fs.readFileSync(result.filePaths[0], 'utf-8')
      const data = JSON.parse(content)
      if (!Array.isArray(data)) throw new Error('JSON 格式错误：应为数组')
      const db = getDb()
      const insert = db.prepare('INSERT INTO exam (name, course, questions_json) VALUES (?, ?, ?)')
      let count = 0
      for (const tpl of data) {
        if (tpl.name && tpl.course) {
          insert.run(tpl.name, tpl.course, JSON.stringify(tpl.questions || []))
          count++
        }
      }
      return { count }
    } catch (err) {
      log.error('template:importJson error:', err)
      return null
    }
  })

  ipcMain.handle('template:save', (_event, data: { name: string; course: string; questions?: any[] }) => {
    try {
      const db = getDb()
      const questionsJson = JSON.stringify(data.questions || [])
      log.info('template:save', { name: data.name, course: data.course, questionCount: data.questions?.length ?? 0, jsonLen: questionsJson.length })
      const result = db.prepare('INSERT INTO exam (name, course, questions_json) VALUES (?, ?, ?)').run(data.name, data.course, questionsJson)
      return result.lastInsertRowid
    } catch (err) {
      log.error('template:save error:', err)
      return null
    }
  })

  ipcMain.handle('template:delete', (_event, id: number) => {
    try {
      getDb().prepare('DELETE FROM exam WHERE id = ?').run(id)
      return true
    } catch {
      return false
    }
  })

  ipcMain.handle('template:apply', (_event, id: number) => {
    try {
      const db = getDb()
      const row = db.prepare('SELECT * FROM exam WHERE id = ?').get(id) as any
      if (!row) throw new Error('模板不存在')
      const questions = row.questions_json ? JSON.parse(row.questions_json) : []
      if (!questions.length) throw new Error('模板中没有题目')
      db.prepare('DELETE FROM rubric').run()
      const insert = db.prepare(
        'INSERT INTO rubric (question_title, max_score, reference_answer, rubric, sort_order, reference_answer_image) VALUES (?, ?, ?, ?, ?, ?)'
      )
      for (let i = 0; i < questions.length; i++) {
        const q = questions[i]
        insert.run(q.questionTitle || '', q.maxScore ?? 100, q.referenceAnswer || '', q.rubric || '', i, q.referenceAnswerImage || '')
      }
      rubricNextIndex = 0
      const mainWin = getMainWindow()
      if (mainWin) mainWin.webContents.send('rubric:updated')
      return true
    } catch (err) {
      log.error('template:apply error:', err)
      return false
    }
  })

  // --- Dialog child windows ---
  ipcMain.handle('dialog:open', (_event, { type, data }: { type: string; data?: any }) => {
    log.info('IPC: dialog:open', type)
    if (data !== undefined) {
      dialogPayloads[type] = data
    }
    openDialogWindow(type)
  })

  ipcMain.on('dialog:close', (_event) => {
    const w = BrowserWindow.fromWebContents(_event.sender)
    if (w) w.close()
  })

  ipcMain.handle('dialog:getPayload', (_event, type: string) => {
    const data = dialogPayloads[type]
    delete dialogPayloads[type]
    return data
  })

  // --- Open image file from disk ---
  ipcMain.handle('dialog:openImage', async () => {
    const result = await dialogBox.showOpenDialog({
      properties: ['openFile'],
      filters: [{ name: '图片', extensions: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'] }]
    })
    if (result.canceled || result.filePaths.length === 0) return null
    const filePath = result.filePaths[0]
    const ext = path.extname(filePath).toLowerCase().replace('.', '')
    const mime = ext === 'jpg' ? 'jpeg' : ext
    const buffer = fs.readFileSync(filePath)
    return { base64: buffer.toString('base64'), mime, filePath }
  })

  // --- Rubric data bridge (toolbar ↔ rubric window), persisted to DB ---
  ipcMain.handle('rubric:storeData', (_event, data: any) => {
    const d = getDb()
    const questions = data?.questions || []
    log.info(`rubric:storeData: ${questions.length} questions`, JSON.stringify(questions.map((q: any) => ({ title: q.questionTitle, rubricLen: q.rubric?.length }))))
    if (questions.length === 0) return
    d.prepare('DELETE FROM rubric').run()
    const insert = d.prepare('INSERT INTO rubric (question_title, max_score, reference_answer, rubric, sort_order, reference_answer_image) VALUES (?, ?, ?, ?, ?, ?)')
    for (let i = 0; i < questions.length; i++) {
      const q = questions[i]
      insert.run(q.questionTitle || '', q.maxScore ?? 100, q.referenceAnswer || '', q.rubric || '', i, q.referenceAnswerImage || '')
    }
  })

  ipcMain.handle('rubric:getData', () => {
    const d = getDb()
    const rows: any[] = d.prepare('SELECT * FROM rubric ORDER BY sort_order').all()
    log.info(`rubric:getData: ${rows.length} rows`)
    if (rows.length) {
      return {
        questions: rows.map(r => {
          const img = r.reference_answer_image
          return {
            questionTitle: r.question_title,
            maxScore: r.max_score,
            referenceAnswer: r.reference_answer,
            rubric: r.rubric,
            referenceAnswerImage: img && !img.startsWith('data:') ? `data:image/png;base64,${img}` : (img || undefined)
          }
        }),
        currentIndex: 0
      }
    }
    return null
  })

  ipcMain.handle('rubric:exportJson', async () => {
    const d = getDb()
    const rows: any[] = d.prepare('SELECT * FROM rubric ORDER BY sort_order').all()
    if (!rows.length) throw new Error('暂无评分标准可导出')
    const questions = rows.map(r => {
      const img = r.reference_answer_image
      return {
        questionTitle: r.question_title,
        maxScore: r.max_score,
        referenceAnswer: r.reference_answer,
        rubric: r.rubric,
        referenceAnswerImage: img && !img.startsWith('data:') ? `data:image/png;base64,${img}` : (img || undefined)
      }
    })
    const result = await dialogBox.showSaveDialog({
      defaultPath: '评分标准.json',
      filters: [{ name: 'JSON', extensions: ['json'] }]
    })
    if (result.canceled || !result.filePath) return false
    fs.writeFileSync(result.filePath, JSON.stringify(questions, null, 2), 'utf-8')
    return true
  })

  ipcMain.handle('rubric:importJson', async () => {
    const result = await dialogBox.showOpenDialog({
      properties: ['openFile'],
      filters: [{ name: 'JSON', extensions: ['json'] }]
    })
    if (result.canceled || result.filePaths.length === 0) return null
    const content = fs.readFileSync(result.filePaths[0], 'utf-8')
    const questions = JSON.parse(content)
    if (!Array.isArray(questions)) throw new Error('JSON 格式错误：应为数组')
    return { questions, currentIndex: 0 }
  })

  // --- Rubric next question index (persisted between grading cycles) ---
  let rubricNextIndex = 0
  ipcMain.handle('rubric:getNextIndex', () => rubricNextIndex)
  ipcMain.handle('rubric:setNextIndex', (_event, idx: number) => { rubricNextIndex = idx })
  ipcMain.handle('rubric:resetNextIndex', () => { rubricNextIndex = 0 })

  // --- Grading data bridge (toolbar → result window) ---
  ipcMain.handle('grading:storeData', (_event, data: any) => {
    gradingPayload = data
  })

  ipcMain.handle('grading:getData', () => gradingPayload)
}

const dialogPayloads: Record<string, any> = {}
let gradingPayload: any = null

async function callAiApi(imageBase64: string, rubric: string, config: { apiKey: string; baseUrl: string; model: string }, extras?: { maxScore?: number; referenceAnswer?: string; questionTitle?: string; referenceAnswerImage?: string }): Promise<{
  score: number
  confidence: number
  deductions: string[]
  comment: string
}> {
  const systemRole = `你是一名高校阅卷老师，根据评分标准和答案对学生答案评分。请输出JSON格式的结果。`

  const hasRefImage = !!extras?.referenceAnswerImage

  const prompt = hasRefImage
    ? `题目：${extras?.questionTitle || ''}
评分标准：${rubric || '无'}
参考答案：${extras?.referenceAnswer || '无'}
满分：${extras?.maxScore ?? 100}

图片1是标准答案，图片2是学生答案。
请根据评分标准和标准答案（图片1）给学生答案（图片2）评分。

评分规则：
1. 逐项评分，各项得分之和不超过满分${extras?.maxScore ?? 100}
2. 空白或无关答案给0分
3. 部分正确酌情给分

请输出JSON：
{"score":分数,"confidence":置信度0~1,"deductions":["扣分项"],"comment":"评语"}

只输出JSON，不要包含其他文字和markdown标记。`
    : `题目：${extras?.questionTitle || ''}
评分标准：${rubric || '无'}
参考答案：${extras?.referenceAnswer || '无'}
满分：${extras?.maxScore ?? 100}

图片是学生答案，请根据评分标准评分。

评分规则：
1. 逐项评分，各项得分之和不超过满分${extras?.maxScore ?? 100}
2. 空白或无关答案给0分
3. 部分正确酌情给分

请输出JSON：
{"score":分数,"confidence":置信度0~1,"deductions":["扣分项"],"comment":"评语"}

只输出JSON，不要包含其他文字和markdown标记。`

  const axios = require('axios')

  const userContent: any[] = hasRefImage
    ? [
        { type: 'text', text: prompt },
        { type: 'image_url', image_url: { url: extras!.referenceAnswerImage } },
        { type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } }
      ]
    : [
        { type: 'text', text: prompt },
        { type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } }
      ]

  let lastError: Error | null = null
  for (let attempt = 1; attempt <= 3; attempt++) {
    try {
      const response = await axios.post(`${config.baseUrl}/chat/completions`, {
        model: config.model,
        messages: [
          {
            role: 'system',
            content: systemRole
          },
          {
            role: 'user',
            content: userContent
          }
        ],
        max_tokens: 4096,
        temperature: 0.3
      }, {
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${config.apiKey}`
        },
        timeout: ((getStore().get('timeout') as number) ?? 120) * 1000
      })

      const content = response.data?.choices?.[0]?.message?.content
      if (!content) {
        lastError = new Error(`API 返回为空 (尝试 ${attempt}/3)`)
        log.warn(`AI API empty response, attempt ${attempt}/3`)
        if (attempt < 3) continue
        throw lastError
      }
      const parsed = tryParseJson(content)
      if (!parsed) {
        lastError = new Error(`AI 返回 JSON 解析失败 (尝试 ${attempt}/3)`)
        log.warn(`AI JSON parse failed, attempt ${attempt}/3:`, content.slice(0, 200))
        if (attempt < 3) continue
        throw lastError
      }
      return {
        score: parsed.score,
        confidence: parsed.confidence ?? 0.5,
        deductions: parsed.deductions ?? [],
        comment: parsed.comment ?? parsed.commit ?? ''
      }
    } catch (err: any) {
      lastError = err
      if (attempt < 3) {
        log.info(`AI API attempt ${attempt}/3 failed, retrying...`)
        await new Promise(r => setTimeout(r, 2000))
      }
    }
  }
  throw lastError || new Error('AI API 调用失败')
}

function tryParseJson(text: string): any {
  let cleaned = text.trim()
  // remove markdown code fences
  cleaned = cleaned.replace(/```(?:json)?\s*/gi, '').replace(/```\s*/g, '').trim()
  // if there's still non-JSON text, extract the first {...} object
  if (!cleaned.startsWith('{') && !cleaned.startsWith('[')) {
    const match = cleaned.match(/\{[\s\S]*\}/)
    if (match) cleaned = match[0]
  }
  // attempt direct parse
  try {
    return JSON.parse(cleaned)
  } catch {}
  // fix single quotes -> double quotes (but keep escaped ones)
  try {
    const fixed = cleaned
      .replace(/(?<!\\)'/g, '"')
      .replace(/,(\s*[}\]])/g, '$1') // trailing commas
    return JSON.parse(fixed)
  } catch {}
  // fix unquoted keys
  try {
    const fixed = cleaned
      .replace(/([{,]\s*)(\w+)(\s*:)/g, '$1"$2"$3')
      .replace(/,(\s*[}\]])/g, '$1')
    return JSON.parse(fixed)
  } catch {}
  return null
}
