import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, In, Repository } from 'typeorm';
import { ImportRun } from './entities/import-run.entity';
import { ImportStep } from './entities/import-step.entity';
import { ImportRow } from './entities/import-row.entity';
import { IMPORT_ACTION_LABELS, IMPORT_STEP_LABELS, IMPORT_STEP_ORDER } from './imports.types';
import type {
  CellValue,
  ImportRowAction,
  ImportRowDecision,
  ImportStepKey,
  StepCommitReceipt,
  StepPreviewSummary,
} from './imports.types';
import { csvCell, parseJson, safeError } from './imports.helpers';
import { writeRow } from './imports.rows';
import { assertStepPermission, findOwnedRun, findStep } from './imports.access';
import type { ImportPrincipal } from './imports.access';

@Injectable()
export class ImportCommitService {
  constructor(
    @InjectRepository(ImportRun)
    private readonly runs: Repository<ImportRun>,
    @InjectRepository(ImportStep)
    private readonly steps: Repository<ImportStep>,
    @InjectRepository(ImportRow)
    private readonly rows: Repository<ImportRow>,
    private readonly dataSource: DataSource,
  ) {}

  async commitStep(
    principal: ImportPrincipal,
    runId: string,
    stepKey: ImportStepKey,
    decisions: ImportRowDecision[],
  ): Promise<StepCommitReceipt> {
    const run = await findOwnedRun(this.runs, principal.id, runId);
    if (run.status === 'committed') {
      const receipt = await this.existingReceipt(run, stepKey);
      return { ...receipt, status: 'already_committed' };
    }
    if (run.currentStepKey !== stepKey) {
      return {
        runId,
        stepKey,
        status: 'conflict',
        created: 0,
        updated: 0,
        skipped: 0,
        failed: 0,
        total: 0,
        nextStepKey: run.currentStepKey,
        runStatus: run.status,
        message: `请先完成「${run.currentStepKey ? IMPORT_STEP_LABELS[run.currentStepKey] : ''}」阶段`,
      };
    }
    const step = await findStep(this.steps, runId, stepKey);
    if (!step || step.status === 'skipped') {
      throw new BadRequestException(`阶段「${IMPORT_STEP_LABELS[stepKey]}」没有分配工作表`);
    }
    if (step.status === 'committed') {
      const receipt = await this.existingReceipt(run, stepKey);
      return { ...receipt, status: 'already_committed' };
    }
    if (step.status !== 'ready') {
      throw new BadRequestException(`阶段「${IMPORT_STEP_LABELS[stepKey]}」尚未预览，请先预览确认`);
    }
    assertStepPermission(principal, stepKey);

    const pendingRows = await this.rows.find({ where: { stepId: step.id, status: 'valid' } });
    const decisionMap = new Map<number, ImportRowAction>();
    for (const decision of decisions ?? []) {
      if (
        Number.isInteger(decision.rowId) &&
        (decision.action === 'create' || decision.action === 'update' || decision.action === 'skip')
      ) {
        decisionMap.set(decision.rowId, decision.action);
      }
    }
    if (pendingRows.length === 0) {
      throw new BadRequestException('没有可提交的有效行，请检查预览结果');
    }
    const rowById = new Map(pendingRows.map((row) => [row.id, row]));
    for (const [rowId, action] of decisionMap) {
      const row = rowById.get(rowId);
      if (!row) continue;
      if (action === 'skip') continue;
      if (!row.action) {
        throw new BadRequestException(
          `第 ${row.rowNumber} 行（${row.sheetName}）没有预览判定，只能选择「跳过」`,
        );
      }
      if (action !== row.action) {
        throw new BadRequestException(
          `第 ${row.rowNumber} 行（${row.sheetName}）预览判定为「${IMPORT_ACTION_LABELS[row.action]}」，不能改为「${IMPORT_ACTION_LABELS[action]}」`,
        );
      }
    }

    run.status = 'committing';
    step.status = 'committing';
    await this.runs.save(run);
    await this.steps.save(step);

    const counts = { created: 0, updated: 0, skipped: 0, failed: 0 };
    try {
      await this.dataSource.transaction(async (manager) => {
        for (const row of pendingRows) {
          const action = decisionMap.get(row.id) ?? row.action ?? 'create';
          if (action === 'skip') {
            row.status = 'skipped';
            row.action = 'skip';
            counts.skipped += 1;
            await manager.save(ImportRow, row);
            continue;
          }
          try {
            const fields = parseJson<Record<string, CellValue>>(row.normalizedJson) ?? {};
            const targetId = await writeRow(manager, stepKey, action, fields, row.targetId);
            row.status = 'committed';
            row.action = action;
            row.targetId = targetId ?? row.targetId;
            if (action === 'create') counts.created += 1;
            else counts.updated += 1;
          } catch (error) {
            row.status = 'error';
            row.errorsJson = JSON.stringify([`写入失败：${safeError(error)}`]);
            counts.failed += 1;
          }
          await manager.save(ImportRow, row);
        }
      });
    } catch (error) {
      run.status = 'failed';
      run.error = safeError(error).slice(0, 500);
      await this.runs.save(run);
      throw new ConflictException(`提交失败：${safeError(error)}`);
    }

    const summary: StepPreviewSummary = {
      total: pendingRows.length,
      valid: counts.created + counts.updated + counts.skipped,
      error: counts.failed,
      create: counts.created,
      update: counts.updated,
      skip: counts.skipped,
    };
    step.status = 'committed';
    step.committedAt = new Date();
    step.summaryJson = JSON.stringify(summary);
    await this.steps.save(step);

    const nextStepKey = await this.nextStepKey(runId, stepKey);
    run.currentStepKey = nextStepKey;
    run.status = nextStepKey ? 'ready' : 'committed';
    await this.runs.save(run);

    const message =
      `阶段「${IMPORT_STEP_LABELS[stepKey]}」提交完成：新建 ${counts.created}、更新 ${counts.updated}、跳过 ${counts.skipped}、失败 ${counts.failed}；` +
      (nextStepKey ? `下一步：${IMPORT_STEP_LABELS[nextStepKey]}` : '全部阶段已完成');
    return {
      runId,
      stepKey,
      status: 'committed',
      created: counts.created,
      updated: counts.updated,
      skipped: counts.skipped,
      failed: counts.failed,
      total: pendingRows.length,
      nextStepKey,
      runStatus: run.status,
      message,
    };
  }

  async errorReport(
    userId: number,
    runId: string,
    stepKey?: ImportStepKey,
  ): Promise<{ filename: string; buffer: Buffer }> {
    const run = await findOwnedRun(this.runs, userId, runId);
    const stepRecords = await this.steps.find({ where: { runId }, order: { id: 'ASC' } });
    const stepIds = stepKey
      ? stepRecords.filter((s) => s.stepKey === stepKey).map((s) => s.id)
      : stepRecords.map((s) => s.id);
    if (stepIds.length === 0) return { filename: '', buffer: Buffer.from('') };
    const rows = await this.rows.find({
      where: { stepId: In(stepIds), status: 'error' },
      order: { id: 'ASC' },
    });
    const lines: string[] = ['工作表,行号,原始数据,错误信息'];
    for (const row of rows) {
      const raw = parseJson<Record<string, CellValue>>(row.rawJson) ?? {};
      const errors = parseJson<string[]>(row.errorsJson) ?? [];
      lines.push(
        [
          csvCell(row.sheetName),
          String(row.rowNumber),
          csvCell(JSON.stringify(raw)),
          csvCell(errors.join('；')),
        ].join(','),
      );
    }
    return {
      filename: `导入错误报告-${run.fileName.replace(/\.(xlsx|csv)$/i, '')}.csv`,
      buffer: Buffer.from(`\uFEFF${lines.join('\n')}`, 'utf8'),
    };
  }

  private async nextStepKey(
    runId: string,
    currentKey: ImportStepKey,
  ): Promise<ImportStepKey | null> {
    const stepRecords = await this.steps.find({ where: { runId } });
    const currentIndex = IMPORT_STEP_ORDER.indexOf(currentKey);
    for (let i = currentIndex + 1; i < IMPORT_STEP_ORDER.length; i += 1) {
      const candidate = IMPORT_STEP_ORDER[i];
      const step = stepRecords.find((s) => s.stepKey === candidate);
      if (step && step.status !== 'skipped' && step.status !== 'committed') {
        return candidate;
      }
    }
    return null;
  }

  private async existingReceipt(
    run: ImportRun,
    stepKey: ImportStepKey,
  ): Promise<Omit<StepCommitReceipt, 'status'>> {
    const step = await findStep(this.steps, run.id, stepKey);
    const summary = parseJson<StepPreviewSummary>(step?.summaryJson);
    return {
      runId: run.id,
      stepKey,
      created: summary?.create ?? 0,
      updated: summary?.update ?? 0,
      skipped: summary?.skip ?? 0,
      failed: summary?.error ?? 0,
      total: summary?.total ?? 0,
      nextStepKey: run.currentStepKey,
      runStatus: run.status,
      message: `阶段「${IMPORT_STEP_LABELS[stepKey]}」此前已提交`,
    };
  }
}
