import {
  BadRequestException,
  Body,
  Controller,
  Get,
  Param,
  ParseUUIDPipe,
  Post,
  Query,
  Res,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import type { Response } from 'express';
import { ImportBatchStatus, ImportBatchType, ImportEntryStatus } from '@prisma/client';
import { JwtAuthGuard } from '@modules/organization/auth/guards/jwt-auth.guard';
import { PermissionsGuard } from '@modules/organization/auth/guards/permissions.guard';
import { RequirePermissions } from '@common/decorators/permissions.decorator';
import { Auditable } from '@core/observability/audit/decorators/auditable.decorator';
import { CurrentUser } from '@common/decorators/current-user.decorator';
import type { CurrentUserPayload } from '@common/decorators/current-user.decorator';
import { NIL_ORG_ID } from '@common/constants/nil-uuid';
import { ImportBatchService } from './import-batch.service';
import { PurchaseOrderImporter } from './purchase-order/purchase-order.importer';
import type { Importer, ValidationIssue } from './importer.interface';
import { PrismaService } from '@core/database/prisma/prisma.service';

// :type URL param → ImportBatchType enum
const TYPE_MAP: Record<string, ImportBatchType> = {
  'purchase-order': ImportBatchType.PURCHASE_ORDER,
  'robot-unit': ImportBatchType.ROBOT_UNIT,
  'master-model': ImportBatchType.MASTER_MODEL,
  'master-sku': ImportBatchType.MASTER_SKU,
  'master-customer': ImportBatchType.MASTER_CUSTOMER,
  'master-supplier': ImportBatchType.MASTER_SUPPLIER,
  'master-location': ImportBatchType.MASTER_LOCATION,
  'service-ticket': ImportBatchType.SERVICE_TICKET,
};

@Controller('robot-manager/import')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class ImportController {
  constructor(
    private readonly importBatchService: ImportBatchService,
    private readonly purchaseOrderImporter: PurchaseOrderImporter,
    private readonly prisma: PrismaService,
  ) {}

  /** importer 注册表 — M2 抽 framework 时改 multi-provider；M1 直接 switch */
  private getImporter(type: ImportBatchType): Importer<any> {
    switch (type) {
      case ImportBatchType.PURCHASE_ORDER:
        return this.purchaseOrderImporter;
      default:
        throw new BadRequestException({ code: 'IMPORT_TYPE_NOT_IMPLEMENTED', params: { type } });
    }
  }

  private resolveType(typeParam: string): ImportBatchType {
    const t = TYPE_MAP[typeParam];
    if (!t) throw new BadRequestException({ code: 'IMPORT_TYPE_INVALID', params: { type: typeParam } });
    return t;
  }

  /** 简化版 isAdmin 判断 — 看 roles 含 'Administrator' / 'ITAdmin' */
  private isAdmin(user: CurrentUserPayload): boolean {
    return user.roles.some((r) => r === 'Administrator' || r === 'ITAdmin');
  }

  // ---------- Template ----------

  @Get(':type/template')
  @RequirePermissions('robot-manager:read')
  async downloadTemplate(
    @Param('type') typeParam: string,
    @Query('locale') locale: 'zh' | 'en' = 'zh',
    @Res() res: Response,
  ) {
    const type = this.resolveType(typeParam);
    const importer = this.getImporter(type);
    const schemaHash = this.importBatchService.computeSchemaHash(importer.fieldMetadata);
    const buffer = this.importBatchService.generateTemplate(importer.fieldMetadata, locale, type);
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    res.setHeader('Content-Disposition', `attachment; filename="${typeParam}-template-${schemaHash.substring(0, 8)}.xlsx"`);
    res.setHeader('X-Template-Schema-Hash', schemaHash);
    res.setHeader('Cache-Control', 'public, max-age=3600');
    res.send(buffer);
  }

  // ---------- Preview ----------

  @Post(':type/preview')
  @Auditable()
  @RequirePermissions('robot-manager:create')
  @UseInterceptors(FileInterceptor('file', { limits: { fileSize: 10 * 1024 * 1024 } }))
  async preview(
    @Param('type') typeParam: string,
    @UploadedFile() file: Express.Multer.File | undefined,
    @CurrentUser() user: CurrentUserPayload,
  ) {
    if (!file) throw new BadRequestException({ code: 'IMPORT_FILE_INVALID', params: { reason: 'no file' } });
    const type = this.resolveType(typeParam);
    const importer = this.getImporter(type);
    const organizationId = NIL_ORG_ID;

    // per-user in-flight 限制 + 同 fileHash 24h idempotent
    const fileHash = this.importBatchService.computeFileHash(file.buffer);
    const existing = await this.importBatchService.findInFlightSameFile(user.userId, organizationId, fileHash);
    if (existing) {
      const errorPreview = existing.errorRows > 0
        ? await this.importBatchService.getErrorPreview(existing.id)
        : [];
      return {
        batchId: existing.id,
        summary: {
          totalRows: existing.totalRows,
          successRows: existing.successRows,
          errorRows: existing.errorRows,
          warningRows: existing.warningRows,
          templateSchemaHash: existing.templateSchemaHash,
          fileHash: existing.fileHash,
          status: existing.status,
          deduped: true,
        },
        errorPreview,
      };
    }
    await this.importBatchService.ensureNoOtherInFlight(user.userId, organizationId);

    // 解析（安全门 + 行数上限）
    const { rows } = this.importBatchService.parseExcel(file.buffer);

    // 解析 + 三层校验
    const parsed = rows.map((r, idx) => importer.parseRow(r, idx + 1));
    const validRowsForRefCheck = parsed; // 即使 typed=null 也加入，refCheck 内部跳过
    const [refIssues, businessIssues] = await Promise.all([
      importer.validateReferences(validRowsForRefCheck),
      importer.validateBusinessRules(validRowsForRefCheck),
    ]);
    const allIssues: ValidationIssue[] = [...refIssues, ...businessIssues];

    const schemaHash = this.importBatchService.computeSchemaHash(importer.fieldMetadata);
    const { batch, errorPreview } = await this.importBatchService.createBatch(
      type,
      file.originalname.replace(/[^A-Za-z0-9_.\-]/g, '_').substring(0, 255),
      fileHash,
      schemaHash,
      parsed,
      allIssues,
      user.userId,
      organizationId,
      undefined,
      undefined,
    );

    return {
      batchId: batch.id,
      summary: {
        totalRows: batch.totalRows,
        successRows: batch.successRows,
        errorRows: batch.errorRows,
        warningRows: batch.warningRows,
        templateSchemaHash: batch.templateSchemaHash,
        fileHash: batch.fileHash,
        status: batch.status,
      },
      errorPreview,
    };
  }

  // ---------- Batches get / list ----------

  @Get('batches/:batchId')
  @RequirePermissions('robot-manager:read')
  async getBatch(
    @Param('batchId', new ParseUUIDPipe()) batchId: string,
    @CurrentUser() user: CurrentUserPayload,
  ) {
    return this.importBatchService.findBatch(batchId, user.userId, NIL_ORG_ID, this.isAdmin(user));
  }

  @Get('batches')
  @RequirePermissions('robot-manager:read')
  async listBatches(
    @Query('type') typeParam: string | undefined,
    @Query('scope') scope: 'mine' | 'all' = 'mine',
    @Query('page') page = '1',
    @Query('limit') limit = '20',
    @CurrentUser() user: CurrentUserPayload,
  ) {
    const type = typeParam ? this.resolveType(typeParam) : undefined;
    return this.importBatchService.listBatches(
      type,
      user.userId,
      NIL_ORG_ID,
      this.isAdmin(user),
      scope,
      Math.max(1, parseInt(page, 10) || 1),
      Math.max(1, parseInt(limit, 10) || 20),
    );
  }

  // ---------- Confirm ----------

  @Post('batches/:batchId/confirm')
  @Auditable()
  @RequirePermissions('robot-manager:create')
  async confirm(
    @Param('batchId', new ParseUUIDPipe()) batchId: string,
    @CurrentUser() user: CurrentUserPayload,
  ) {
    const organizationId = NIL_ORG_ID;

    // 1. CAS 锁
    const locked = await this.importBatchService.lockForConfirm(batchId, user.userId, organizationId);
    if (!locked) throw new BadRequestException({ code: 'IMPORT_BATCH_NOT_FOUND' });

    // 2. 检查 errorRows
    if (locked.errorRows > 0) {
      await this.importBatchService.markFailed(batchId, 'IMPORT_BATCH_HAS_ERRORS', { errorRows: locked.errorRows });
      throw new BadRequestException({ code: 'IMPORT_BATCH_HAS_ERRORS', params: { n: String(locked.errorRows) } });
    }

    const importer = this.getImporter(locked.type);
    const okEntries = locked.entries.filter((e) => e.status === ImportEntryStatus.OK);

    // 3. 重跑校验防 TOCTOU
    const reparsed = okEntries.map((e) => importer.parseRow(e.payload as Record<string, unknown>, e.rowNo));
    const [refIssues, businessIssues] = await Promise.all([
      importer.validateReferences(reparsed),
      importer.validateBusinessRules(reparsed),
    ]);
    const driftIssues = [...refIssues, ...businessIssues];
    if (driftIssues.length > 0) {
      await this.importBatchService.markFailed(batchId, 'IMPORT_REFS_CHANGED_RETRY', { issues: driftIssues.slice(0, 10) });
      throw new BadRequestException({
        code: 'IMPORT_REFS_CHANGED_RETRY',
        params: { issueCount: String(driftIssues.length) },
        details: driftIssues.slice(0, 10),
      });
    }

    // 4. 执行写库 — 全 all-or-nothing
    try {
      const typedRows = reparsed.map((r) => r.typed).filter((t): t is NonNullable<typeof t> => t != null);
      const result = await this.prisma.$transaction(async (tx) => {
        return importer.execute(typedRows, tx, user.userId, organizationId);
      });

      // 回写 entityIds 到 entries
      const entityIdsMap = new Map<number, string[]>();
      for (const r of result.rowResults) entityIdsMap.set(r.rowNo, r.entityIds);
      const completed = await this.importBatchService.markCompleted(batchId, user.userId, entityIdsMap);

      return {
        batchId,
        status: completed.status,
        completedAt: completed.completedAt,
        successRows: completed.successRows,
        sideEffects: result.sideEffects,
      };
    } catch (e: any) {
      await this.importBatchService.markFailed(batchId, 'IMPORT_EXECUTE_FAILED', { error: e.message });
      throw e;
    }
  }

  // ---------- Error Report ----------

  @Get('batches/:batchId/error-report.xlsx')
  @RequirePermissions('robot-manager:read')
  async downloadErrorReport(
    @Param('batchId', new ParseUUIDPipe()) batchId: string,
    @CurrentUser() user: CurrentUserPayload,
    @Res() res: Response,
  ) {
    const buffer = await this.importBatchService.generateErrorReport(batchId, user.userId, NIL_ORG_ID, this.isAdmin(user));
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    res.setHeader('Content-Disposition', `attachment; filename="${batchId}-errors.xlsx"`);
    res.send(buffer);
  }
}
