import { BadRequestException, Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { APP_DATA_KEYS } from '../data/data-keys';
import { mapAppDataToV22Rows, type V22MappedRows } from './app-data-v22.mapper';

const COUNT_KEYS = [
  'users',
  'products',
  'projects',
  'versions',
  'taskCategories',
  'requirements',
  'versionPlans',
  'devTasks',
  'testCases',
  'bugs',
  'workActivities',
  'taskWorklogs',
  'overtimeRecords',
  'xiaobaoRiskSnapshots',
  'xiaobaoRiskInsights',
  'xiaobaoRiskSummaries',
  'aiLogs',
] as const;

type CountKey = (typeof COUNT_KEYS)[number];
type DelegateName =
  | 'user'
  | 'product'
  | 'project'
  | 'version'
  | 'taskCategory'
  | 'requirement'
  | 'versionPlan'
  | 'devTask'
  | 'testCase'
  | 'bug'
  | 'workActivity'
  | 'taskWorklog'
  | 'overtimeRecord'
  | 'xiaobaoRiskSnapshot'
  | 'xiaobaoRiskInsight'
  | 'xiaobaoRiskSummary';

type CreateManyDelegate = {
  createMany(args: { data: unknown[]; skipDuplicates: boolean }): Promise<{ count: number }>;
};

type MigrationTransaction = Record<DelegateName, CreateManyDelegate>;

const DELEGATE_BY_COUNT_KEY: Record<CountKey, DelegateName> = {
  users: 'user',
  products: 'product',
  projects: 'project',
  versions: 'version',
  taskCategories: 'taskCategory',
  requirements: 'requirement',
  versionPlans: 'versionPlan',
  devTasks: 'devTask',
  testCases: 'testCase',
  bugs: 'bug',
  workActivities: 'workActivity',
  taskWorklogs: 'taskWorklog',
  overtimeRecords: 'overtimeRecord',
  xiaobaoRiskSnapshots: 'xiaobaoRiskSnapshot',
  xiaobaoRiskInsights: 'xiaobaoRiskInsight',
  xiaobaoRiskSummaries: 'xiaobaoRiskSummary',
  aiLogs: 'user',
};

const DATE_TIME_KEYS = new Set([
  'createdAt',
  'updatedAt',
  'releaseDate',
  'expectedStartAt',
  'expectedEndAt',
  'actualStartAt',
  'completedAt',
  'startDate',
  'plannedTestAt',
  'plannedEndAt',
  'startedAt',
  'plannedFixAt',
  'resolvedAt',
  'closedAt',
  'startAt',
  'endAt',
  'occurredAt',
  'aiDraftAt',
  'recomputedAt',
  'forecastReleaseDate',
  'snapshotDate',
  'workDate',
]);

export interface AppDataV22MigrationPreview {
  readyToImport: boolean;
  counts: Record<CountKey, number>;
  skipped: V22MappedRows['skipped'];
}

export interface AppDataV22MigrationImportResult extends AppDataV22MigrationPreview {
  inserted: Record<CountKey, number>;
}

@Injectable()
export class AppDataV22MigrationService {
  constructor(private readonly prisma: PrismaService) {}

  async loadSnapshot(): Promise<Record<string, unknown>> {
    const rows = await this.prisma.appData.findMany({
      where: { key: { in: [...APP_DATA_KEYS] } },
      select: { key: true, value: true },
    });
    return Object.fromEntries(rows.map((row) => [row.key, row.value]));
  }

  mapSnapshot(snapshot: Record<string, unknown>): V22MappedRows {
    return mapAppDataToV22Rows(snapshot);
  }

  async preview(): Promise<AppDataV22MigrationPreview> {
    const mapped = this.mapSnapshot(await this.loadSnapshot());
    return {
      readyToImport: mapped.skipped.length === 0,
      counts: countRows(mapped),
      skipped: mapped.skipped,
    };
  }

  async importCurrentAppData(): Promise<AppDataV22MigrationImportResult> {
    return this.importMappedRows(this.mapSnapshot(await this.loadSnapshot()));
  }

  async importMappedRows(mapped: V22MappedRows): Promise<AppDataV22MigrationImportResult> {
    if (mapped.skipped.length > 0) {
      throw new BadRequestException({
        code: 'APP_DATA_V22_MIGRATION_HAS_SKIPPED_ROWS',
        message: 'Resolve skipped AppData rows before importing V2.2 relation tables.',
        skipped: mapped.skipped,
      });
    }

    const inserted = await this.prisma.$transaction(async (tx) => {
      const client = tx as unknown as MigrationTransaction;
      const result = emptyCounts();
      for (const key of COUNT_KEYS) {
        if (key === 'aiLogs') continue;
        result[key] = await createMany(client[DELEGATE_BY_COUNT_KEY[key]], mapped[key]);
      }
      return result;
    });

    return {
      readyToImport: true,
      counts: countRows(mapped),
      skipped: [],
      inserted,
    };
  }
}

function countRows(mapped: V22MappedRows): Record<CountKey, number> {
  return Object.fromEntries(COUNT_KEYS.map((key) => [key, mapped[key].length])) as Record<CountKey, number>;
}

function emptyCounts(): Record<CountKey, number> {
  return Object.fromEntries(COUNT_KEYS.map((key) => [key, 0])) as Record<CountKey, number>;
}

async function createMany(delegate: CreateManyDelegate, data: unknown[]): Promise<number> {
  if (data.length === 0) return 0;
  const result = await delegate.createMany({ data: data.map(normalizeDateTimeFields), skipDuplicates: true });
  return result.count;
}

export function normalizeDateTimeFields(row: unknown): unknown {
  if (typeof row !== 'object' || row === null || Array.isArray(row)) return row;
  return Object.fromEntries(
    Object.entries(row).map(([key, value]) => [
      key,
      DATE_TIME_KEYS.has(key) ? normalizeDateTimeValue(value) : value,
    ]),
  );
}

function normalizeDateTimeValue(value: unknown): unknown {
  if (typeof value !== 'string') return value;
  if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return `${value}T00:00:00.000Z`;
  return value;
}
