import { DataSource } from 'typeorm';
import { Organization } from '../entities/organization.entity';
import { buildLookups } from './imports.lookups';
import { suggestMapping, suggestStep } from './imports.mapping';
import { validateRow, type ImportBatchState } from './imports.rows';
import {
  IMPORT_STEP_IDENTITY_FIELDS,
  IMPORT_STEP_LABELS,
  IMPORT_STEP_ORDER,
  IMPORT_STEP_REQUIRED_FIELDS,
} from './imports.types';
import type {
  CellValue,
  ColumnMapping,
  ImportStepKey,
  PreflightBlock,
  PreflightBlockCode,
  PreflightErrorSample,
  PreflightNextStep,
  PreflightQuestion,
  PreflightReport,
  PreflightStageStat,
} from './imports.types';
import type { ImportSheetData } from './imports.workbook';

const BLOCK_META: Record<PreflightBlockCode, { label: string; message: string }> = {
  no_stages: {
    label: '未识别工作表',
    message: '没有识别到可导入的学生、宿舍、入住或换宿工作表，请检查表头',
  },
  missing_columns: {
    label: '缺少必填列',
    message: '阶段缺少必需列映射，无法自动导入',
  },
  student_not_found: {
    label: '未找到学生',
    message: '部分行找不到匹配学生，需先完成学生档案或核对学号/手机号',
  },
  room_not_found: {
    label: '未找到宿舍',
    message: '部分行找不到匹配宿舍，需先完成宿舍档案或核对宿舍号',
  },
  duplicate_in_file: {
    label: '文件内重复',
    message: '同一文件内存在重复在住/换宿记录',
  },
  already_checked_in: {
    label: '已有在住',
    message: '学生已有在住记录，重复入住会被拦截',
  },
  format_error: {
    label: '格式错误',
    message: '部分行存在格式或取值错误（日期、手机号、容量等）',
  },
  unknown_organization: {
    label: '未知校区',
    message: '部分行填写的校区不存在，需要确认归属',
  },
};

const REQUIRED_FIELD_LABELS: Record<string, string> = {
  name: '姓名',
  roomNumber: '宿舍号',
  capacity: '容量',
  checkInDate: '入住日期',
  oldRoom: '原宿舍',
  newRoom: '新宿舍',
  transferDate: '换宿日期',
  identity: '学号或手机号',
};

const NEXT_STEP_DEFS: Array<PreflightNextStep> = [
  {
    key: 'students-next',
    label: '分班 / 排课 / 入住',
    description: '学生档案导入完成后，可继续分班、排课或录入入住记录。',
    after: ['students'],
  },
  {
    key: 'rooms-next',
    label: '入住 / 费用',
    description: '宿舍档案导入完成后，可录入入住记录并维护宿舍费用。',
    after: ['rooms'],
  },
  {
    key: 'checkins-next',
    label: '费用 / 账单',
    description: '入住记录导入完成后，可录入公共费用并生成账单。',
    after: ['checkins'],
  },
  {
    key: 'transfers-next',
    label: '账单核对',
    description: '换宿完成后建议核对在住记录与账单，避免计费偏差。',
    after: ['transfers'],
  },
];

interface StageAnalysis extends PreflightStageStat {
  rowErrorCodes: PreflightBlockCode[];
  unknownOrgs: string[];
  errorSamples: PreflightErrorSample[];
}

function classifyErrors(errors: string[]): PreflightBlockCode[] {
  const codes = new Set<PreflightBlockCode>();
  for (const error of errors) {
    if (
      error.includes('未找到匹配学生') ||
      error.includes('缺少学生标识') ||
      error.includes('未找到该学生在原宿舍的在住记录')
    ) {
      codes.add('student_not_found');
    } else if (
      error.includes('未找到宿舍') ||
      error.includes('未找到原宿舍') ||
      error.includes('未找到新宿舍')
    ) {
      codes.add('room_not_found');
    } else if (
      error.includes('请勿重复导入') ||
      error.includes('请勿重复换宿') ||
      error.includes('本次文件中已有')
    ) {
      codes.add('duplicate_in_file');
    } else if (error.includes('已有在住记录')) {
      codes.add('already_checked_in');
    } else if (error.includes('未找到校区')) {
      codes.add('unknown_organization');
    } else {
      codes.add('format_error');
    }
  }
  return [...codes];
}

async function analyzeStage(
  dataSource: DataSource,
  stepKey: ImportStepKey,
  sheets: ImportSheetData[],
): Promise<StageAnalysis> {
  const firstSheet = sheets[0];
  const mapping: ColumnMapping = suggestMapping(firstSheet.headers, stepKey);
  const required = IMPORT_STEP_REQUIRED_FIELDS[stepKey];
  const missingRequired = required
    .filter((field) => !mapping[field])
    .map((field) => REQUIRED_FIELD_LABELS[field] ?? field);
  const identityFields = IMPORT_STEP_IDENTITY_FIELDS[stepKey];
  const hasIdentity = identityFields.some((field) => mapping[field]);

  let total = 0;
  let create = 0;
  let update = 0;
  let error = 0;
  const rowErrorCodes: PreflightBlockCode[] = [];
  const errorSamples: PreflightErrorSample[] = [];
  const sampleCounts = new Map<PreflightBlockCode, number>();
  const unknownOrgs = new Set<string>();
  const batchState: ImportBatchState = {
    checkinStudentIds: new Set<number>(),
    transferStudentIds: new Set<number>(),
  };

  for (const sheet of sheets) {
    const lookups = await buildLookups(dataSource, stepKey, sheet.headers, sheet.rows, mapping);
    for (let i = 0; i < sheet.rows.length; i += 1) {
      const rawValues = sheet.rows[i];
      const fields: Record<string, CellValue> = {};
      for (const [field, header] of Object.entries(mapping)) {
        fields[field] = rawValues[sheet.headers.indexOf(header)] ?? null;
      }
      const result = validateRow(stepKey, fields, lookups, batchState);
      total += 1;
      if (result.errors.length > 0) {
        error += 1;
        const codes = classifyErrors(result.errors);
        rowErrorCodes.push(...codes);
        for (const code of codes) {
          const count = sampleCounts.get(code) ?? 0;
          if (count < 2) {
            sampleCounts.set(code, count + 1);
            errorSamples.push({
              code,
              stepKey,
              sheet: sheet.name,
              rowNumber: i + 2,
              errors: result.errors,
            });
          }
        }
        if (stepKey === 'students' && result.errors.some((item) => item.includes('未找到校区'))) {
          const org = String(fields.organization ?? '');
          if (org) unknownOrgs.add(org);
        }
      } else if (result.action === 'create') {
        create += 1;
        const studentId = result.resolvedIds._studentId;
        if (studentId !== undefined) {
          if (stepKey === 'checkins') batchState.checkinStudentIds.add(studentId);
          if (stepKey === 'transfers') batchState.transferStudentIds.add(studentId);
        }
      } else if (result.action === 'update') {
        update += 1;
      }
    }
  }

  return {
    stepKey,
    label: IMPORT_STEP_LABELS[stepKey],
    sheetNames: sheets.map((sheet) => sheet.name),
    total,
    create,
    update,
    error,
    skip: 0,
    mapping,
    missingRequired: hasIdentity
      ? missingRequired
      : [...new Set([...missingRequired, REQUIRED_FIELD_LABELS.identity])],
    rowErrorCodes,
    unknownOrgs: [...unknownOrgs],
    errorSamples,
  };
}

function aggregateBlocks(stages: StageAnalysis[]): PreflightBlock[] {
  const counts = new Map<PreflightBlockCode, number>();
  const stepKeys = new Map<PreflightBlockCode, Set<ImportStepKey>>();
  const add = (code: PreflightBlockCode, stepKey: ImportStepKey, count: number) => {
    counts.set(code, (counts.get(code) ?? 0) + count);
    const keys = stepKeys.get(code) ?? new Set<ImportStepKey>();
    keys.add(stepKey);
    stepKeys.set(code, keys);
  };
  for (const stage of stages) {
    if (stage.missingRequired.length > 0) {
      add('missing_columns', stage.stepKey, stage.total);
    }
    for (const code of stage.rowErrorCodes) {
      add(code, stage.stepKey, 1);
    }
  }
  return [...counts.entries()]
    .map(([code, count]) => ({
      code,
      label: BLOCK_META[code].label,
      stepKeys: [...(stepKeys.get(code) ?? [])],
      message: BLOCK_META[code].message,
      count,
    }))
    .sort((a, b) => b.count - a.count);
}

function buildQuestions(
  stages: StageAnalysis[],
  existingOrganizations: string[],
): PreflightQuestion[] {
  const questions: PreflightQuestion[] = [];
  for (const stage of stages) {
    if (stage.missingRequired.length > 0) {
      questions.push({
        key: `mapping_${stage.stepKey}`,
        type: 'mapping',
        label: `确认「${stage.label}」列映射`,
        description: `缺少必需列映射：${stage.missingRequired.join('、')}；请确认工作表中对应的列名`,
        stepKey: stage.stepKey,
      });
    }
  }
  const totalUpdates = stages.reduce((sum, stage) => sum + stage.update, 0);
  if (totalUpdates > 0) {
    questions.push({
      key: 'update',
      type: 'update',
      label: `文件中有 ${totalUpdates} 行已匹配现有记录`,
      description: '选择更新已有记录，或跳过已匹配的行（仅新建）',
      options: [
        { label: '更新已有记录', value: 'true' },
        { label: '跳过已有记录', value: 'false' },
      ],
      default: true,
    });
  }
  const unknownOrgs = [...new Set(stages.flatMap((stage) => stage.unknownOrgs))];
  if (unknownOrgs.length > 0) {
    const options = [
      ...existingOrganizations.slice(0, 19).map((name) => ({ label: name, value: name })),
      { label: '忽略校区', value: '' },
    ];
    questions.push({
      key: 'organization',
      type: 'organization',
      label: '确认校区归属',
      description: `文件中存在未匹配的校区：${unknownOrgs.join('、')}，请选择实际归属校区`,
      options,
    });
  }
  if (stages.some((stage) => stage.rowErrorCodes.includes('duplicate_in_file'))) {
    questions.push({
      key: 'duplicate',
      type: 'duplicate',
      label: '文件内存在重复在住/换宿记录',
      description: '选择将重复行标记为错误，或按策略跳过重复行',
      options: [
        { label: '标记为错误', value: 'error' },
        { label: '跳过重复行', value: 'skip' },
      ],
      default: 'error',
    });
  }
  if (
    stages.some((stage) =>
      stage.rowErrorCodes.some(
        (code) => code === 'student_not_found' || code === 'room_not_found',
      ),
    )
  ) {
    questions.push({
      key: 'reference',
      type: 'reference',
      label: '存在未匹配的学生或宿舍',
      description: '选择保留错误提示，或跳过找不到学生/宿舍的行继续导入',
      options: [
        { label: '保留错误提示', value: 'false' },
        { label: '跳过未匹配行', value: 'true' },
      ],
      default: false,
    });
  }
  return questions;
}

function decideVerdict(
  stages: StageAnalysis[],
  questions: PreflightQuestion[],
  hasStages: boolean,
): PreflightReport['verdict'] {
  if (!hasStages) return 'blocked';
  if (stages.some((stage) => stage.missingRequired.length > 0)) return 'blocked';
  if (
    stages.some(
      (stage) =>
        stage.total > 0 &&
        stage.total === stage.error &&
        stage.rowErrorCodes.length > 0 &&
        stage.rowErrorCodes.every((code) => code === 'format_error'),
    )
  ) {
    return 'blocked';
  }
  if (questions.length > 0) return 'needs_input';
  return 'ready';
}

/**
 * 生成“可插入性预检报告”：按业务依赖分阶段统计，归类阻断原因，
 * 给出需要用户确认的问题与导入后的下一步建议。纯读操作，不写库。
 */
export async function buildPreflightReport(
  dataSource: DataSource,
  sheets: ImportSheetData[],
): Promise<PreflightReport> {
  const grouped = new Map<ImportStepKey, ImportSheetData[]>();
  for (const sheet of sheets) {
    const suggestion = suggestStep(sheet.headers);
    if (!suggestion) continue;
    const list = grouped.get(suggestion.stepKey) ?? [];
    list.push(sheet);
    grouped.set(suggestion.stepKey, list);
  }
  const stageKeys = IMPORT_STEP_ORDER.filter((stepKey) => grouped.has(stepKey));
  const hasStages = stageKeys.length > 0;

  const stages: StageAnalysis[] = [];
  const existingOrganizations = new Set<string>();
  if (hasStages) {
    for (const stepKey of stageKeys) {
      const analysis = await analyzeStage(dataSource, stepKey, grouped.get(stepKey) ?? []);
      stages.push(analysis);
    }
    const organizations = await dataSource
      .getRepository(Organization)
      .find({ select: { name: true } });
    for (const org of organizations) existingOrganizations.add(org.name);
  }

  const blocks = aggregateBlocks(stages);
  if (!hasStages) {
    blocks.push({
      code: 'no_stages',
      label: BLOCK_META.no_stages.label,
      stepKeys: [],
      message: BLOCK_META.no_stages.message,
      count: sheets.length,
    });
  }
  const questions = buildQuestions(stages, [...existingOrganizations]);
  const detectedKeys = new Set(stages.map((stage) => stage.stepKey));
  const nextSteps = NEXT_STEP_DEFS.filter((step) => step.after.some((key) => detectedKeys.has(key)));

  return {
    verdict: decideVerdict(stages, questions, hasStages),
    stages: stages.map(
      ({
        rowErrorCodes: _rowErrorCodes,
        unknownOrgs: _unknownOrgs,
        errorSamples: _errorSamples,
        ...stat
      }) => stat,
    ),
    blocks,
    questions,
    nextSteps,
    errorSamples: stages.flatMap((stage) => stage.errorSamples),
  };
}
