import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { randomUUID } from 'node:crypto';
import { Repository } from 'typeorm';
import { ImportRun } from './entities/import-run.entity';
import { ImportStep } from './entities/import-step.entity';
import { IMPORT_STEP_LABELS, IMPORT_STEP_ORDER } from './imports.types';
import type {
  CellValue,
  ColumnMapping,
  ImportRunSettings,
  ImportRunSource,
  ImportStageRequest,
  ImportStepKey,
  ParsedImportFile,
  StepPreviewSummary,
} from './imports.types';
import { parseJson } from './imports.helpers';
import { parseSheets } from './imports.workbook';
import { autoAssignedSheets, resolveAssignedSheets, suggestMapping, suggestStep } from './imports.mapping';
import { findOwnedRun } from './imports.access';
import type { ImportPrincipal } from './imports.access';

@Injectable()
export class ImportRunService {
  constructor(
    @InjectRepository(ImportRun)
    private readonly runs: Repository<ImportRun>,
    @InjectRepository(ImportStep)
    private readonly steps: Repository<ImportStep>,
  ) {}

  async createRun(
    principal: ImportPrincipal,
    source: ImportRunSource,
    file: ParsedImportFile,
    conversationId?: number | null,
    stages?: ImportStageRequest[],
    mappingByStep?: Partial<Record<ImportStepKey, ColumnMapping>>,
    settings?: ImportRunSettings,
  ) {
    if (!file.buffer || file.buffer.length === 0) {
      throw new BadRequestException('上传文件为空');
    }
    const sheets = await parseSheets(file.buffer, file.originalName, file.mimeType);

    const runId = randomUUID();
    const run = this.runs.create({
      id: runId,
      userId: principal.id,
      conversationId: conversationId ?? null,
      source,
      fileName: file.originalName.slice(0, 255),
      sheetsJson: JSON.stringify(sheets),
      settingsJson: settings ? JSON.stringify(settings) : null,
      status: 'ready',
      currentStepKey: null,
      error: null,
    });

    const stepRecords: ImportStep[] = [];
    for (const stepKey of IMPORT_STEP_ORDER) {
      const assigned =
        stages && stages.length > 0
          ? resolveAssignedSheets(
              stages,
              stepKey,
              sheets.map((s) => s.name),
            )
          : autoAssignedSheets(sheets, stepKey);
      if (assigned.length === 0) {
        stepRecords.push(
          this.steps.create({
            runId,
            stepKey,
            sheetsJson: '[]',
            mappingJson: null,
            status: 'skipped',
            summaryJson: null,
            committedAt: null,
          }),
        );
        continue;
      }
      const firstSheet = sheets.find((s) => s.name === assigned[0]);
      const mapping =
        mappingByStep?.[stepKey] ?? suggestMapping(firstSheet?.headers ?? [], stepKey);
      stepRecords.push(
        this.steps.create({
          runId,
          stepKey,
          sheetsJson: JSON.stringify(assigned),
          mappingJson: mapping ? JSON.stringify(mapping) : null,
          status: 'pending',
          summaryJson: null,
          committedAt: null,
        }),
      );
    }
    const firstActive = stepRecords.find((s) => s.status !== 'skipped');
    run.currentStepKey = firstActive?.stepKey ?? null;
    await this.runs.save(run);
    await this.steps.save(stepRecords);
    return this.getRun(principal.id, runId);
  }

  async getRun(userId: number, runId: string) {
    const run = await findOwnedRun(this.runs, userId, runId);
    const stepRecords = await this.steps.find({ where: { runId }, order: { id: 'ASC' } });
    const sheets =
      parseJson<Array<{ name: string; headers: string[]; rows: CellValue[][] }>>(run.sheetsJson) ??
      [];
    return {
      id: run.id,
      fileName: run.fileName,
      source: run.source,
      status: run.status,
      currentStepKey: run.currentStepKey,
      createdAt: run.createdAt.toISOString(),
      sheets: sheets.map((sheet) => ({
        name: sheet.name,
        headers: sheet.headers,
        rowCount: sheet.rows.length,
        suggestedStepKey: suggestStep(sheet.headers)?.stepKey ?? null,
      })),
      settings: parseJson<ImportRunSettings>(run.settingsJson) ?? {},
      steps: stepRecords.map((step) => ({
        id: step.id,
        stepKey: step.stepKey,
        label: IMPORT_STEP_LABELS[step.stepKey],
        sheets: parseJson<string[]>(step.sheetsJson) ?? [],
        status: step.status,
        mapping: parseJson<ColumnMapping>(step.mappingJson) ?? {},
        summary: parseJson<StepPreviewSummary>(step.summaryJson),
        committedAt: step.committedAt?.toISOString() ?? null,
      })),
    };
  }
}
