import { v4 as uuidv4 } from 'uuid';
import { get, query, run } from '../database';

export type QuestionType = 'single' | 'multiple' | 'judgment' | 'text';

export interface ExamSubject {
  id: string;
  name: string;
  totalScore: number;
  timeLimitMinutes: number;
  typeRatios: Record<QuestionType, number>;
  categoryRatios: Record<string, number>;
  createdAt: string;
  updatedAt: string;
}

type RawSubjectRow = {
  id: string;
  name: string;
  typeRatios: string;
  categoryRatios: string;
  totalScore: number;
  timeLimitMinutes: number;
  createdAt: string;
  updatedAt: string;
};

const parseJson = <T>(value: string, fallback: T): T => {
  try {
    return JSON.parse(value) as T;
  } catch {
    return fallback;
  }
};

const validateNonNegativeNumbers = (valuesMap: Record<string, number>, label: string) => {
  const values = Object.values(valuesMap);
  if (values.length === 0) throw new Error(`${label}不能为空`);
  if (values.some((v) => typeof v !== 'number' || Number.isNaN(v) || v < 0)) {
    throw new Error(`${label}必须是非负数字`);
  }
  if (!values.some((v) => v > 0)) {
    throw new Error(`${label}至少需要一个大于0的配置`);
  }
};

const validateIntegerCounts = (valuesMap: Record<string, number>, label: string) => {
  validateNonNegativeNumbers(valuesMap, label);
  if (Object.values(valuesMap).some((v) => !Number.isInteger(v))) {
    throw new Error(`${label}必须为整数`);
  }
};

const sumValues = (valuesMap: Record<string, number>) => Object.values(valuesMap).reduce((a, b) => a + b, 0);

const isRatioMode = (valuesMap: Record<string, number>) => {
  const values = Object.values(valuesMap);
  if (values.length === 0) return false;
  if (values.some((v) => typeof v !== 'number' || Number.isNaN(v) || v < 0)) return false;
  if (values.some((v) => v > 100)) return false;
  const sum = values.reduce((a, b) => a + b, 0);
  return Math.abs(sum - 100) <= 0.01;
};

export class ExamSubjectModel {
  static async generateQuizQuestions(subject: ExamSubject): Promise<{
    questions: Awaited<ReturnType<typeof import('./question').QuestionModel.getRandomQuestions>>;
    totalScore: number;
    timeLimitMinutes: number;
  }> {
    const { QuestionModel } = await import('./question');
    let questions: Awaited<ReturnType<typeof QuestionModel.getRandomQuestions>> = [];

    const typeRatioMode = isRatioMode(subject.typeRatios as any);
    const categoryRatioMode = isRatioMode(subject.categoryRatios);
    if (typeRatioMode !== categoryRatioMode) {
      throw new Error('题型配置与题目类别配置必须同为比例或同为数量');
    }

    const categoryEntries = Object.entries(subject.categoryRatios).filter(([, v]) => v > 0);
    const categories = categoryEntries.map(([c]) => c);
    if (categories.length === 0) categories.push('通用');

    if (typeRatioMode) {
      const weightedCategories: string[] = [];
      for (const [category, ratio] of categoryEntries) {
        const weight = Math.min(100, Math.max(0, Math.round(ratio)));
        for (let i = 0; i < weight; i++) weightedCategories.push(category);
      }
      if (weightedCategories.length === 0) {
        weightedCategories.push('通用');
      }

      for (const [type, typeRatio] of Object.entries(subject.typeRatios)) {
        if (typeRatio <= 0) continue;

        const targetTypeScore = Math.round((typeRatio / 100) * subject.totalScore);
        let currentTypeScore = 0;

        while (currentTypeScore < targetTypeScore) {
          const randomCategory = weightedCategories[Math.floor(Math.random() * weightedCategories.length)];
          const availableQuestions = await QuestionModel.getRandomQuestions(type as any, 100, [randomCategory]);

          if (availableQuestions.length === 0) break;

          const availableUnselected = availableQuestions.filter((q) => !questions.some((selected) => selected.id === q.id));
          if (availableUnselected.length === 0) break;

          const remainingForType = targetTypeScore - currentTypeScore;
          const selectedQuestion = availableUnselected.reduce((prev, curr) => {
            const prevDiff = Math.abs(remainingForType - prev.score);
            const currDiff = Math.abs(remainingForType - curr.score);
            return currDiff < prevDiff ? curr : prev;
          });

          questions.push(selectedQuestion);
          currentTypeScore += selectedQuestion.score;

          if (questions.length > 200) break;
        }
      }
      let totalScore = questions.reduce((sum, q) => sum + q.score, 0);
      while (totalScore < subject.totalScore) {
        const allTypes = Object.keys(subject.typeRatios).filter((t) => (subject.typeRatios as any)[t] > 0);
        if (allTypes.length === 0) break;

        const randomType = allTypes[Math.floor(Math.random() * allTypes.length)];
        const availableQuestions = await QuestionModel.getRandomQuestions(randomType as any, 100, categories);
        if (availableQuestions.length === 0) break;

        const availableUnselected = availableQuestions.filter((q) => !questions.some((selected) => selected.id === q.id));
        if (availableUnselected.length === 0) break;

        const remainingScore = subject.totalScore - totalScore;
        const selectedQuestion = availableUnselected.reduce((prev, curr) => {
          const prevDiff = Math.abs(remainingScore - prev.score);
          const currDiff = Math.abs(remainingScore - curr.score);
          return currDiff < prevDiff ? curr : prev;
        });

        questions.push(selectedQuestion);
        totalScore += selectedQuestion.score;

        if (questions.length > 200) break;
      }

      while (totalScore > subject.totalScore) {
        const excessScore = totalScore - subject.totalScore;
        let closestIndex = -1;
        let closestDiff = Infinity;

        for (let i = 0; i < questions.length; i++) {
          const diff = Math.abs(questions[i].score - excessScore);
          if (diff < closestDiff) {
            closestDiff = diff;
            closestIndex = i;
          }
        }

        if (closestIndex === -1) break;
        totalScore -= questions[closestIndex].score;
        questions.splice(closestIndex, 1);
      }

      const uniqueQuestions = questions.filter((q, index, self) => 
        index === self.findIndex((t) => t.id === q.id)
      );

      return {
        questions: uniqueQuestions,
        totalScore: uniqueQuestions.reduce((sum, q) => sum + q.score, 0),
        timeLimitMinutes: subject.timeLimitMinutes,
      };
    }

    const categoryRemaining = new Map<string, number>();
    for (const [category, count] of categoryEntries) {
      const c = Math.max(0, Math.round(count));
      if (c > 0) categoryRemaining.set(category, c);
    }
    if (categoryRemaining.size === 0) {
      categoryRemaining.set('通用', sumValues(subject.typeRatios as any));
      if (!categories.includes('通用')) categories.push('通用');
    }

    const pickCategory = (exclude?: Set<string>) => {
      const entries = Array.from(categoryRemaining.entries()).filter(([, c]) => c > 0);
      const usable = exclude ? entries.filter(([k]) => !exclude.has(k)) : entries;
      if (usable.length === 0) return null;
      const total = usable.reduce((s, [, c]) => s + c, 0);
      let r = Math.floor(Math.random() * total);
      for (const [k, c] of usable) {
        if (r < c) return k;
        r -= c;
      }
      return usable[0][0];
    };

    let currentTotal = 0;
    let remainingSlots = sumValues(Object.fromEntries(categoryRemaining) as any);

    for (const [type, rawCount] of Object.entries(subject.typeRatios)) {
      const targetCount = Math.max(0, Math.round(rawCount));
      if (targetCount <= 0) continue;

      for (let i = 0; i < targetCount; i++) {
        const tried = new Set<string>();
        let selected = null as null | Awaited<ReturnType<typeof QuestionModel.getRandomQuestions>>[number];
        let selectedCategory: string | null = null;

        for (let attempt = 0; attempt < categoryRemaining.size; attempt++) {
          const category = pickCategory(tried);
          if (!category) break;
          tried.add(category);

          const desiredAvg = remainingSlots > 0 ? (subject.totalScore - currentTotal) / remainingSlots : 0;
          const fetched = await QuestionModel.getRandomQuestions(type as any, 100, [category]);
          const candidates = fetched.filter((q) => !questions.some((selectedQ) => selectedQ.id === q.id));
          if (candidates.length === 0) continue;

          selected = candidates.reduce((prev, curr) => {
            const prevDiff = Math.abs(desiredAvg - prev.score);
            const currDiff = Math.abs(desiredAvg - curr.score);
            return currDiff < prevDiff ? curr : prev;
          });
          selectedCategory = category;
          break;
        }

        if (!selected || !selectedCategory) {
          continue;
        }

        questions.push(selected);
        currentTotal += selected.score;
        categoryRemaining.set(selectedCategory, (categoryRemaining.get(selectedCategory) || 0) - 1);
        remainingSlots -= 1;
      }
    }

    let totalScore = questions.reduce((sum, q) => sum + q.score, 0);
    for (let i = 0; i < 30; i++) {
      const diff = subject.totalScore - totalScore;
      if (diff === 0) break;
      if (questions.length === 0) break;

      const idx = Math.floor(Math.random() * questions.length);
      const base = questions[idx];
      const fetched = await QuestionModel.getRandomQuestions(base.type as any, 100, [base.category]);
      const candidates = fetched.filter((q) => !questions.some((selectedQ) => selectedQ.id === q.id) && q.id !== base.id);
      if (candidates.length === 0) continue;

      const currentBest = Math.abs(diff);
      let best = null as null | (typeof candidates)[number];
      let bestAbs = currentBest;

      for (const cand of candidates) {
        const nextTotal = totalScore - base.score + cand.score;
        const abs = Math.abs(subject.totalScore - nextTotal);
        if (abs < bestAbs) {
          bestAbs = abs;
          best = cand;
        }
      }

      if (!best) continue;
      questions[idx] = best;
      totalScore = totalScore - base.score + best.score;
    }

    const uniqueQuestions = questions.filter((q, index, self) => 
      index === self.findIndex((t) => t.id === q.id)
    );

    return {
      questions: uniqueQuestions,
      totalScore: uniqueQuestions.reduce((sum, q) => sum + q.score, 0),
      timeLimitMinutes: subject.timeLimitMinutes,
    };
  }

  static async findAll(): Promise<ExamSubject[]> {
    const sql = `
      SELECT 
        id,
        name,
        type_ratios as typeRatios,
        category_ratios as categoryRatios,
        total_score as totalScore,
        duration_minutes as timeLimitMinutes,
        created_at as createdAt,
        updated_at as updatedAt
      FROM exam_subjects
      ORDER BY created_at DESC
    `;

    const rows: RawSubjectRow[] = await query(sql);
    return rows.map((row) => ({
      id: row.id,
      name: row.name,
      totalScore: row.totalScore,
      timeLimitMinutes: row.timeLimitMinutes,
      typeRatios: parseJson<Record<QuestionType, number>>(row.typeRatios, {
        single: 40,
        multiple: 30,
        judgment: 20,
        text: 10
      }),
      categoryRatios: parseJson<Record<string, number>>(row.categoryRatios, {}),
      createdAt: row.createdAt,
      updatedAt: row.updatedAt
    }));
  }

  static async findById(id: string): Promise<ExamSubject | null> {
    const sql = `
      SELECT 
        id,
        name,
        type_ratios as typeRatios,
        category_ratios as categoryRatios,
        total_score as totalScore,
        duration_minutes as timeLimitMinutes,
        created_at as createdAt,
        updated_at as updatedAt
      FROM exam_subjects
      WHERE id = ?
    `;

    const row: RawSubjectRow | undefined = await get(sql, [id]);
    if (!row) return null;

    return {
      id: row.id,
      name: row.name,
      totalScore: row.totalScore,
      timeLimitMinutes: row.timeLimitMinutes,
      typeRatios: parseJson<Record<QuestionType, number>>(row.typeRatios, {
        single: 40,
        multiple: 30,
        judgment: 20,
        text: 10
      }),
      categoryRatios: parseJson<Record<string, number>>(row.categoryRatios, {}),
      createdAt: row.createdAt,
      updatedAt: row.updatedAt
    };
  }

  static async create(data: {
    name: string;
    totalScore: number;
    timeLimitMinutes?: number;
    typeRatios: Record<QuestionType, number>;
    categoryRatios?: Record<string, number>;
  }): Promise<ExamSubject> {
    const name = data.name.trim();
    if (!name) throw new Error('科目名称不能为空');
    if (!data.totalScore || data.totalScore <= 0) throw new Error('总分必须为正整数');

    const timeLimitMinutes = data.timeLimitMinutes ?? 60;
    if (!Number.isInteger(timeLimitMinutes) || timeLimitMinutes <= 0) throw new Error('答题时间必须为正整数(分钟)');

    const categoryRatios = data.categoryRatios && Object.keys(data.categoryRatios).length > 0 ? data.categoryRatios : {};
    const typeRatioMode = isRatioMode(data.typeRatios as any);
    const categoryRatioMode = isRatioMode(categoryRatios);
    if (typeRatioMode !== categoryRatioMode) {
      throw new Error('题型配置与题目类别配置必须同为比例或同为数量');
    }
    if (typeRatioMode) {
      validateNonNegativeNumbers(data.typeRatios as any, '题型配置');
      validateNonNegativeNumbers(categoryRatios, '题目类别配置');
    } else {
      validateIntegerCounts(data.typeRatios as any, '题型数量配置');
      validateIntegerCounts(categoryRatios, '题目类别数量配置');
      const typeTotal = sumValues(data.typeRatios as any);
      const categoryTotal = sumValues(categoryRatios);
      if (typeTotal !== categoryTotal) {
        throw new Error('题型数量总和必须等于题目类别数量总和');
      }
    }

    const id = uuidv4();
    const sql = `
      INSERT INTO exam_subjects (
        id, name, type_ratios, category_ratios, total_score, duration_minutes, updated_at
      ) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
    `;

    try {
      await run(sql, [
        id,
        name,
        JSON.stringify(data.typeRatios),
        JSON.stringify(categoryRatios),
        data.totalScore,
        timeLimitMinutes
      ]);
    } catch (error: any) {
      if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
        throw new Error('科目名称已存在');
      }
      throw error;
    }

    return (await this.findById(id)) as ExamSubject;
  }

  static async update(id: string, data: {
    name: string;
    totalScore: number;
    timeLimitMinutes?: number;
    typeRatios: Record<QuestionType, number>;
    categoryRatios?: Record<string, number>;
  }): Promise<ExamSubject> {
    const existing = await this.findById(id);
    if (!existing) throw new Error('科目不存在');

    const name = data.name.trim();
    if (!name) throw new Error('科目名称不能为空');
    if (!data.totalScore || data.totalScore <= 0) throw new Error('总分必须为正整数');

    const timeLimitMinutes = data.timeLimitMinutes ?? 60;
    if (!Number.isInteger(timeLimitMinutes) || timeLimitMinutes <= 0) throw new Error('答题时间必须为正整数(分钟)');

    const categoryRatios = data.categoryRatios && Object.keys(data.categoryRatios).length > 0 ? data.categoryRatios : {};
    const typeRatioMode = isRatioMode(data.typeRatios as any);
    const categoryRatioMode = isRatioMode(categoryRatios);
    if (typeRatioMode !== categoryRatioMode) {
      throw new Error('题型配置与题目类别配置必须同为比例或同为数量');
    }
    if (typeRatioMode) {
      validateNonNegativeNumbers(data.typeRatios as any, '题型配置');
      validateNonNegativeNumbers(categoryRatios, '题目类别配置');
    } else {
      validateIntegerCounts(data.typeRatios as any, '题型数量配置');
      validateIntegerCounts(categoryRatios, '题目类别数量配置');
      const typeTotal = sumValues(data.typeRatios as any);
      const categoryTotal = sumValues(categoryRatios);
      if (typeTotal !== categoryTotal) {
        throw new Error('题型数量总和必须等于题目类别数量总和');
      }
    }

    const sql = `
      UPDATE exam_subjects 
      SET name = ?, type_ratios = ?, category_ratios = ?, total_score = ?, duration_minutes = ?, updated_at = datetime('now')
      WHERE id = ?
    `;

    try {
      await run(sql, [
        name,
        JSON.stringify(data.typeRatios),
        JSON.stringify(categoryRatios),
        data.totalScore,
        timeLimitMinutes,
        id
      ]);
    } catch (error: any) {
      if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
        throw new Error('科目名称已存在');
      }
      throw error;
    }

    return (await this.findById(id)) as ExamSubject;
  }

  static async delete(id: string): Promise<void> {
    const existing = await this.findById(id);
    if (!existing) throw new Error('科目不存在');

    const taskCount = await get(`SELECT COUNT(*) as total FROM exam_tasks WHERE subject_id = ?`, [id]);
    if (taskCount && taskCount.total > 0) {
      throw new Error('该科目已被考试任务使用，无法删除');
    }

    await run(`DELETE FROM exam_subjects WHERE id = ?`, [id]);
  }
}
