import { BadRequestException } from '@nestjs/common';
import {
  IMPORT_FIELD_ALIASES,
  IMPORT_STEP_IDENTITY_FIELDS,
  IMPORT_STEP_LABELS,
  IMPORT_STEP_ORDER,
  IMPORT_STEP_REQUIRED_FIELDS,
} from './imports.types';
import type {
  ColumnMapping,
  ImportStageRequest,
  ImportStageSuggestion,
  ImportStepKey,
} from './imports.types';
import { headerMatches } from './imports.helpers';
import type { ImportSheetData } from './imports.workbook';

export function suggestMapping(headers: string[], stepKey: ImportStepKey): ColumnMapping {
  const mapping: ColumnMapping = {};
  for (const [field, aliases] of Object.entries(IMPORT_FIELD_ALIASES[stepKey])) {
    const found = headers.find((header) => aliases.some((alias) => headerMatches(header, alias)));
    if (found) mapping[field] = found;
  }
  return mapping;
}

/** 归一化阶段的工作表清单：sheets 优先，否则回退 sheet 单表，都没有则返回空数组。 */
export function expandStageSheets(stage: ImportStageRequest): string[] {
  const names = stage.sheets?.map((name) => name?.trim()).filter(Boolean) ?? [];
  if (names.length > 0) return [...new Set(names)];
  const single = stage.sheet?.trim();
  return single ? [single] : [];
}

/**
 * 按工作表解析列映射：字段优先使用确认映射（该表存在对应表头时），
 * 否则回退该表自身的表头建议，避免异构表头导致整行取不到值。
 */
export function resolveSheetMapping(
  confirmed: ColumnMapping | null | undefined,
  headers: string[],
  stepKey: ImportStepKey,
): ColumnMapping {
  const suggested = suggestMapping(headers, stepKey);
  const mapping: ColumnMapping = {};
  const fieldNames = new Set([
    ...Object.keys(confirmed ?? {}),
    ...Object.keys(suggested),
  ]);
  for (const field of fieldNames) {
    const header = confirmed?.[field];
    if (header && headers.includes(header)) {
      mapping[field] = header;
    } else if (suggested[field]) {
      mapping[field] = suggested[field];
    }
  }
  return mapping;
}

export function suggestStep(headers: string[]): ImportStageSuggestion | null {
  let best: ImportStageSuggestion | null = null;
  for (const stepKey of IMPORT_STEP_ORDER) {
    const mapping = suggestMapping(headers, stepKey);
    if (Object.keys(mapping).length === 0) continue;
    const identity = IMPORT_STEP_IDENTITY_FIELDS[stepKey].filter(
      (field) => mapping[field],
    ).length;
    const required = IMPORT_STEP_REQUIRED_FIELDS[stepKey].filter(
      (field) => mapping[field],
    ).length;
    // 显式信号优先：入住表出现“入住日期/姓名”时应优先归入入住；
    // 换宿表出现“原宿舍/新宿舍/换宿日期”时应优先归入换宿，
    // 避免“日期/宿舍号”这类模糊别名把入住表误判成换宿表。
    const hasAny = (aliases: string[]) =>
      headers.some((header) => aliases.some((alias) => headerMatches(header, alias)));
    const signal =
      stepKey === 'checkins' &&
      (hasAny(['入住日期', '入住时间']) ||
        (hasAny(['宿舍号', '房间号', '房号']) && hasAny(['入住日期', '入住时间', '日期'])))
        ? 6
        : stepKey === 'transfers' &&
            hasAny(['原宿舍', '原房间', '原宿舍号', '新宿舍', '新房间', '新宿舍号', '换宿日期', '变更日期'])
          ? 6
          : 0;
    const score = Object.keys(mapping).length + identity * 3 + required * 2 + signal;
    if (!best || score > best.matchedFields) {
      best = { stepKey, mapping, matchedFields: score };
    }
  }
  return best;
}

export function autoAssignedSheets(
  sheets: ImportSheetData[],
  stepKey: ImportStepKey,
): string[] {
  return sheets
    .filter((sheet) => suggestStep(sheet.headers)?.stepKey === stepKey)
    .map((sheet) => sheet.name);
}

export function resolveAssignedSheets(
  stages: ImportStageRequest[],
  stepKey: ImportStepKey,
  available: string[],
): string[] {
  const names = stages
    .filter((stage) => stage.stepKey === stepKey)
    .flatMap(expandStageSheets);
  const missing = names.filter((name) => !available.includes(name));
  if (missing.length > 0) {
    throw new BadRequestException(`工作表不存在：${missing.join('、')}`);
  }
  return [...new Set(names)];
}

export function assertMapping(
  stepKey: ImportStepKey,
  mapping: ColumnMapping,
  sheetsData: ImportSheetData[],
  usedSheets: string[],
): void {
  const required = IMPORT_STEP_REQUIRED_FIELDS[stepKey];
  const missingRequired = required.filter((field) => !mapping[field]);
  if (missingRequired.length > 0) {
    const labels: Record<string, string> = {
      name: '姓名',
      roomNumber: '宿舍号',
      capacity: '容量',
      checkInDate: '入住日期',
      oldRoom: '原宿舍',
      newRoom: '新宿舍',
      transferDate: '换宿日期',
    };
    throw new BadRequestException(
      `阶段「${IMPORT_STEP_LABELS[stepKey]}」缺少必需列映射：${missingRequired
        .map((field) => labels[field] ?? field)
        .join('、')}`,
    );
  }
  if (stepKey === 'students' || stepKey === 'checkins' || stepKey === 'transfers') {
    const hasIdentity = IMPORT_STEP_IDENTITY_FIELDS[stepKey].some((field) => mapping[field]);
    if (!hasIdentity) {
      throw new BadRequestException('请至少映射“学号”或“手机号”列用于匹配学生');
    }
  }
  const usedHeaders = new Set(
    usedSheets.flatMap((name) => sheetsData.find((s) => s.name === name)?.headers ?? []),
  );
  const missingHeaders = Object.values(mapping).filter((header) => !usedHeaders.has(header));
  if (missingHeaders.length > 0) {
    throw new BadRequestException(`映射的列不存在于所选工作表：${missingHeaders.join('、')}`);
  }
}
