import { createHash } from 'crypto';
import { Prisma, PrismaClient } from '@prisma/client';

type PrismaLike = PrismaClient | Prisma.TransactionClient;

type SeedResult = {
  empresa_id: string;
  empleados_usados: number;
  relojes: { creados: number; existentes: number };
  turnos: { creados: number; existentes: number };
  asignaciones: { creadas: number; existentes: number };
  tolerancias: { creadas: number; existentes: number };
  permisos: { creados: number; existentes: number };
  marcaciones_raw: { creadas: number; existentes: number };
  marcaciones_limpias: { creadas: number; actualizadas: number };
  log_correcciones: { creados: number; existentes: number };
  ajustes_manuales: { creados: number; existentes: number };
  novedades: { creadas: number; existentes: number };
};

const SEED_TAG = 'SEED_PRESENTISMO_V1';

function deterministicUuid(namespace: string, key: string): string {
  const raw = createHash('sha1').update(`${namespace}:${key}`).digest('hex').slice(0, 32).split('');
  raw[12] = '5';
  raw[16] = ((parseInt(raw[16], 16) & 0x3) | 0x8).toString(16);
  return `${raw.slice(0, 8).join('')}-${raw.slice(8, 12).join('')}-${raw.slice(12, 16).join('')}-${raw.slice(16, 20).join('')}-${raw.slice(20, 32).join('')}`;
}

function startOfDayUtc(date: Date): Date {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
}

function addDaysUtc(date: Date, days: number): Date {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
}

function timeUtc(hh: number, mm: number): Date {
  return new Date(Date.UTC(1970, 0, 1, hh, mm, 0, 0));
}

function timestampUtc(date: Date, hh: number, mm: number): Date {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), hh, mm, 0, 0));
}

function dateBetweenUtc(date: Date) {
  const from = startOfDayUtc(date);
  const to = addDaysUtc(from, 1);
  return { gte: from, lt: to };
}

async function ensureReloj(
  prisma: PrismaLike,
  empresaId: string,
  data: {
    nombre: string;
    tipo_conexion: 'API' | 'PLANILLA';
    sucursal_id?: string | null;
    url_base?: string | null;
    api_key?: string | null;
    tipo_auth?: 'NONE' | 'BASIC' | 'BEARER' | 'API_KEY' | null;
    formato_respuesta?: string | null;
    campo_documento?: string | null;
    campo_timestamp?: string | null;
    intervalo_polling_min?: number | null;
    activo?: boolean;
  },
  counters: { creados: number; existentes: number },
) {
  const found = await prisma.rrhh_relojes_marcadores.findFirst({
    where: { empresa_id: empresaId, nombre: data.nombre },
    select: { id: true },
  });

  if (found) {
    counters.existentes += 1;
    await prisma.rrhh_relojes_marcadores.update({
      where: { id: found.id },
      data: {
        tipo_conexion: data.tipo_conexion,
        sucursal_id: data.sucursal_id ?? null,
        url_base: data.url_base ?? null,
        api_key: data.api_key ?? null,
        tipo_auth: data.tipo_auth ?? null,
        formato_respuesta: data.formato_respuesta ?? null,
        campo_documento: data.campo_documento ?? null,
        campo_timestamp: data.campo_timestamp ?? null,
        intervalo_polling_min: data.intervalo_polling_min ?? null,
        activo: data.activo ?? true,
        updated_at: new Date(),
      },
    });
    return found.id;
  }

  const created = await prisma.rrhh_relojes_marcadores.create({
    data: {
      empresa_id: empresaId,
      nombre: data.nombre,
      tipo_conexion: data.tipo_conexion,
      sucursal_id: data.sucursal_id ?? null,
      url_base: data.url_base ?? null,
      api_key: data.api_key ?? null,
      tipo_auth: data.tipo_auth ?? null,
      formato_respuesta: data.formato_respuesta ?? null,
      campo_documento: data.campo_documento ?? null,
      campo_timestamp: data.campo_timestamp ?? null,
      intervalo_polling_min: data.intervalo_polling_min ?? null,
      activo: data.activo ?? true,
    },
    select: { id: true },
  });

  counters.creados += 1;
  return created.id;
}

async function ensureTurno(
  prisma: PrismaLike,
  empresaId: string,
  data: {
    nombre: string;
    descripcion: string;
    tipo: 'SIMPLE' | 'CORTADO';
    bloques: Array<{ orden: number; hora_entrada: [number, number]; hora_salida: [number, number] }>;
  },
  counters: { creados: number; existentes: number },
) {
  const found = await prisma.rrhh_turnos.findFirst({
    where: { empresa_id: empresaId, nombre: data.nombre },
    select: { id: true },
  });

  let turnoId: string;
  if (found) {
    turnoId = found.id;
    counters.existentes += 1;
    await prisma.rrhh_turnos.update({
      where: { id: turnoId },
      data: {
        descripcion: data.descripcion,
        tipo: data.tipo,
        lunes: true,
        martes: true,
        miercoles: true,
        jueves: true,
        viernes: true,
        sabado: false,
        domingo: false,
        activo: true,
        updated_at: new Date(),
      },
    });
  } else {
    const created = await prisma.rrhh_turnos.create({
      data: {
        empresa_id: empresaId,
        nombre: data.nombre,
        descripcion: data.descripcion,
        tipo: data.tipo,
        lunes: true,
        martes: true,
        miercoles: true,
        jueves: true,
        viernes: true,
        sabado: false,
        domingo: false,
        activo: true,
      },
      select: { id: true },
    });
    turnoId = created.id;
    counters.creados += 1;
  }

  await prisma.rrhh_turnos_bloques.deleteMany({ where: { turno_id: turnoId } });
  await prisma.rrhh_turnos_bloques.createMany({
    data: data.bloques.map((b) => ({
      turno_id: turnoId,
      orden: b.orden,
      hora_entrada: timeUtc(b.hora_entrada[0], b.hora_entrada[1]),
      hora_salida: timeUtc(b.hora_salida[0], b.hora_salida[1]),
    })),
  });

  return turnoId;
}

async function ensureAsignacion(
  prisma: PrismaLike,
  empresaId: string,
  data: {
    empleado_id: string;
    turno_id: string;
    tipo_asignacion: 'PERPETUA' | 'OCASIONAL';
    fecha_inicio: Date | null;
    fecha_fin: Date | null;
    prioridad: number;
    observacion: string;
  },
  counters: { creadas: number; existentes: number },
) {
  const existing = await prisma.rrhh_empleados_turnos.findFirst({
    where: {
      empresa_id: empresaId,
      empleado_id: data.empleado_id,
      turno_id: data.turno_id,
      tipo_asignacion: data.tipo_asignacion,
      prioridad: data.prioridad,
      fecha_inicio: data.fecha_inicio,
      fecha_fin: data.fecha_fin,
    },
    select: { id: true },
  });

  if (existing) {
    counters.existentes += 1;
    return existing.id;
  }

  const created = await prisma.rrhh_empleados_turnos.create({
    data: {
      empresa_id: empresaId,
      empleado_id: data.empleado_id,
      turno_id: data.turno_id,
      tipo_asignacion: data.tipo_asignacion,
      fecha_inicio: data.fecha_inicio,
      fecha_fin: data.fecha_fin,
      prioridad: data.prioridad,
      observacion: data.observacion,
    },
    select: { id: true },
  });
  counters.creadas += 1;
  return created.id;
}

async function ensurePermiso(
  prisma: PrismaLike,
  empresaId: string,
  data: {
    empleado_id: string;
    fecha: Date;
    tipo_permiso: Prisma.rrhh_permisos_presentismoUncheckedCreateInput['tipo_permiso'];
    turno_bloque_orden?: number | null;
    hora_desde?: Date | null;
    hora_hasta?: Date | null;
    minutos_tolerados?: number | null;
    motivo: string;
    tipo_justificacion: Prisma.rrhh_permisos_presentismoUncheckedCreateInput['tipo_justificacion'];
    afecta_descuento: boolean;
    estado: Prisma.rrhh_permisos_presentismoUncheckedCreateInput['estado'];
    aprobado_por?: string | null;
    fecha_aprobacion?: Date | null;
    rechazado_por?: string | null;
    fecha_rechazo?: Date | null;
    creado_por?: string | null;
  },
  counters: { creados: number; existentes: number },
) {
  const existing = await prisma.rrhh_permisos_presentismo.findFirst({
    where: {
      empresa_id: empresaId,
      empleado_id: data.empleado_id,
      fecha: dateBetweenUtc(data.fecha),
      tipo_permiso: data.tipo_permiso,
      motivo: data.motivo,
    },
    select: { id: true },
  });
  if (existing) {
    counters.existentes += 1;
    await prisma.rrhh_permisos_presentismo.update({
      where: { id: existing.id },
      data: {
        turno_bloque_orden: data.turno_bloque_orden ?? null,
        hora_desde: data.hora_desde ?? null,
        hora_hasta: data.hora_hasta ?? null,
        minutos_tolerados: data.minutos_tolerados ?? null,
        tipo_justificacion: data.tipo_justificacion,
        afecta_descuento: data.afecta_descuento,
        estado: data.estado,
        aprobado_por: data.aprobado_por ?? null,
        fecha_aprobacion: data.fecha_aprobacion ?? null,
        rechazado_por: data.rechazado_por ?? null,
        fecha_rechazo: data.fecha_rechazo ?? null,
        updated_at: new Date(),
      },
    });
    return existing.id;
  }

  const created = await prisma.rrhh_permisos_presentismo.create({
    data: {
      empresa_id: empresaId,
      empleado_id: data.empleado_id,
      fecha: startOfDayUtc(data.fecha),
      tipo_permiso: data.tipo_permiso,
      turno_bloque_orden: data.turno_bloque_orden ?? null,
      hora_desde: data.hora_desde ?? null,
      hora_hasta: data.hora_hasta ?? null,
      minutos_tolerados: data.minutos_tolerados ?? null,
      motivo: data.motivo,
      tipo_justificacion: data.tipo_justificacion,
      afecta_descuento: data.afecta_descuento,
      estado: data.estado,
      aprobado_por: data.aprobado_por ?? null,
      fecha_aprobacion: data.fecha_aprobacion ?? null,
      rechazado_por: data.rechazado_por ?? null,
      fecha_rechazo: data.fecha_rechazo ?? null,
      creado_por: data.creado_por ?? null,
    },
    select: { id: true },
  });
  counters.creados += 1;
  return created.id;
}

export async function seedPresentismoDemo(prisma: PrismaLike, empresaId: string): Promise<SeedResult> {
  const empresa = await prisma.empresas.findUnique({
    where: { id: empresaId },
    select: { id: true },
  });
  if (!empresa) {
    throw new Error(`La empresa ${empresaId} no existe.`);
  }

  const empleados = await prisma.rrhh_empleados.findMany({
    where: { empresa_id: empresaId, estado: 'ACTIVO' },
    orderBy: [{ created_at: 'asc' }],
    take: 8,
    select: {
      id: true,
      cedula_identidad: true,
      sucursal_id: true,
      departamento_id: true,
    },
  });
  if (empleados.length < 6) {
    throw new Error(
      `Se requieren al menos 6 empleados activos para el seed de presentismo. ` +
        `Ejecute primero: npm run seed:rrhh:empleados -- --empresa-id=${empresaId}`,
    );
  }

  const actor = await prisma.usuario.findFirst({
    where: { empresa_id: empresaId },
    orderBy: { created_at: 'asc' },
    select: { id: true },
  });
  const actorId = actor?.id ?? deterministicUuid(empresaId, `${SEED_TAG}:actor`);

  const baseDate = startOfDayUtc(new Date());
  const fechaD1 = addDaysUtc(baseDate, -1);
  const fechaD2 = addDaysUtc(baseDate, -2);
  const fechaD3 = addDaysUtc(baseDate, -3);

  const batchMain = deterministicUuid(empresaId, `${SEED_TAG}:batch:main`);
  const batchDup = deterministicUuid(empresaId, `${SEED_TAG}:batch:dup`);
  const batchErr = deterministicUuid(empresaId, `${SEED_TAG}:batch:err`);

  const result: SeedResult = {
    empresa_id: empresaId,
    empleados_usados: empleados.length,
    relojes: { creados: 0, existentes: 0 },
    turnos: { creados: 0, existentes: 0 },
    asignaciones: { creadas: 0, existentes: 0 },
    tolerancias: { creadas: 0, existentes: 0 },
    permisos: { creados: 0, existentes: 0 },
    marcaciones_raw: { creadas: 0, existentes: 0 },
    marcaciones_limpias: { creadas: 0, actualizadas: 0 },
    log_correcciones: { creados: 0, existentes: 0 },
    ajustes_manuales: { creados: 0, existentes: 0 },
    novedades: { creadas: 0, existentes: 0 },
  };

  const relojApiId = await ensureReloj(
    prisma,
    empresaId,
    {
      nombre: `${SEED_TAG} - RELOJ API`,
      tipo_conexion: 'API',
      sucursal_id: empleados[0].sucursal_id,
      url_base: 'https://reloj-demo.local/api',
      api_key: deterministicUuid(empresaId, `${SEED_TAG}:api_key`),
      tipo_auth: 'API_KEY',
      formato_respuesta: 'JSON',
      campo_documento: 'ci',
      campo_timestamp: 'timestamp',
      intervalo_polling_min: 15,
      activo: true,
    },
    result.relojes,
  );

  const relojPlanillaId = await ensureReloj(
    prisma,
    empresaId,
    {
      nombre: `${SEED_TAG} - RELOJ PLANILLA`,
      tipo_conexion: 'PLANILLA',
      sucursal_id: empleados[0].sucursal_id,
      formato_respuesta: 'CSV',
      campo_documento: 'documento',
      campo_timestamp: 'fecha_hora',
      intervalo_polling_min: null,
      activo: true,
    },
    result.relojes,
  );

  const turnoSimpleId = await ensureTurno(
    prisma,
    empresaId,
    {
      nombre: `${SEED_TAG} - TURNO SIMPLE`,
      descripcion: 'Turno administrativo de prueba (08:00-17:00)',
      tipo: 'SIMPLE',
      bloques: [{ orden: 1, hora_entrada: [8, 0], hora_salida: [17, 0] }],
    },
    result.turnos,
  );

  const turnoCortadoId = await ensureTurno(
    prisma,
    empresaId,
    {
      nombre: `${SEED_TAG} - TURNO CORTADO`,
      descripcion: 'Turno operativo de prueba (07:00-11:30 / 13:00-17:30)',
      tipo: 'CORTADO',
      bloques: [
        { orden: 1, hora_entrada: [7, 0], hora_salida: [11, 30] },
        { orden: 2, hora_entrada: [13, 0], hora_salida: [17, 30] },
      ],
    },
    result.turnos,
  );

  await ensureAsignacion(
    prisma,
    empresaId,
    {
      empleado_id: empleados[0].id,
      turno_id: turnoSimpleId,
      tipo_asignacion: 'PERPETUA',
      fecha_inicio: null,
      fecha_fin: null,
      prioridad: 100,
      observacion: `${SEED_TAG} asignación perpetua simple`,
    },
    result.asignaciones,
  );
  await ensureAsignacion(
    prisma,
    empresaId,
    {
      empleado_id: empleados[1].id,
      turno_id: turnoSimpleId,
      tipo_asignacion: 'PERPETUA',
      fecha_inicio: null,
      fecha_fin: null,
      prioridad: 100,
      observacion: `${SEED_TAG} asignación perpetua simple`,
    },
    result.asignaciones,
  );
  await ensureAsignacion(
    prisma,
    empresaId,
    {
      empleado_id: empleados[2].id,
      turno_id: turnoSimpleId,
      tipo_asignacion: 'PERPETUA',
      fecha_inicio: null,
      fecha_fin: null,
      prioridad: 100,
      observacion: `${SEED_TAG} asignación perpetua simple`,
    },
    result.asignaciones,
  );
  await ensureAsignacion(
    prisma,
    empresaId,
    {
      empleado_id: empleados[3].id,
      turno_id: turnoCortadoId,
      tipo_asignacion: 'PERPETUA',
      fecha_inicio: null,
      fecha_fin: null,
      prioridad: 100,
      observacion: `${SEED_TAG} asignación perpetua cortado`,
    },
    result.asignaciones,
  );
  await ensureAsignacion(
    prisma,
    empresaId,
    {
      empleado_id: empleados[4].id,
      turno_id: turnoCortadoId,
      tipo_asignacion: 'PERPETUA',
      fecha_inicio: null,
      fecha_fin: null,
      prioridad: 100,
      observacion: `${SEED_TAG} asignación perpetua cortado`,
    },
    result.asignaciones,
  );
  await ensureAsignacion(
    prisma,
    empresaId,
    {
      empleado_id: empleados[5].id,
      turno_id: turnoCortadoId,
      tipo_asignacion: 'OCASIONAL',
      fecha_inicio: addDaysUtc(baseDate, 1),
      fecha_fin: addDaysUtc(baseDate, 5),
      prioridad: 10,
      observacion: `${SEED_TAG} cobertura ocasional`,
    },
    result.asignaciones,
  );

  const tolerancias = [
    {
      fecha: fechaD1,
      tolerancia_entrada_min: 20,
      tolerancia_salida_min: 20,
      motivo: `${SEED_TAG} tolerancia general por lluvia`,
      aplica_a: 'TODOS' as const,
      aplica_a_id: null as string | null,
    },
    {
      fecha: fechaD2,
      tolerancia_entrada_min: 10,
      tolerancia_salida_min: 5,
      motivo: `${SEED_TAG} tolerancia por sucursal`,
      aplica_a: 'SUCURSAL' as const,
      aplica_a_id: empleados[0].sucursal_id,
    },
    {
      fecha: fechaD3,
      tolerancia_entrada_min: 15,
      tolerancia_salida_min: 15,
      motivo: `${SEED_TAG} tolerancia por departamento`,
      aplica_a: 'DEPARTAMENTO' as const,
      aplica_a_id: empleados[0].departamento_id,
    },
  ];

  for (const tol of tolerancias) {
    const existing = await prisma.rrhh_presentismo_tolerancias_especiales.findFirst({
      where: {
        empresa_id: empresaId,
        fecha: dateBetweenUtc(tol.fecha),
        aplica_a: tol.aplica_a,
        aplica_a_id: tol.aplica_a_id,
        motivo: tol.motivo,
      },
      select: { id: true },
    });
    if (existing) {
      result.tolerancias.existentes += 1;
      continue;
    }
    await prisma.rrhh_presentismo_tolerancias_especiales.create({
      data: {
        empresa_id: empresaId,
        fecha: startOfDayUtc(tol.fecha),
        tolerancia_entrada_min: tol.tolerancia_entrada_min,
        tolerancia_salida_min: tol.tolerancia_salida_min,
        motivo: tol.motivo,
        aplica_a: tol.aplica_a,
        aplica_a_id: tol.aplica_a_id,
        creado_por: actorId,
      },
    });
    result.tolerancias.creadas += 1;
  }

  const permiso1 = await ensurePermiso(
    prisma,
    empresaId,
    {
      empleado_id: empleados[0].id,
      fecha: fechaD1,
      tipo_permiso: 'JORNADA_COMPLETA',
      motivo: `${SEED_TAG} permiso jornada completa aprobado`,
      tipo_justificacion: 'MEDICA',
      afecta_descuento: false,
      estado: 'APROBADO',
      aprobado_por: actorId,
      fecha_aprobacion: timestampUtc(fechaD1, 7, 0),
      creado_por: actorId,
    },
    result.permisos,
  );

  const permiso2 = await ensurePermiso(
    prisma,
    empresaId,
    {
      empleado_id: empleados[1].id,
      fecha: fechaD2,
      tipo_permiso: 'LLEGADA_TARDIA',
      minutos_tolerados: 25,
      motivo: `${SEED_TAG} tolerancia de llegada pendiente`,
      tipo_justificacion: 'PERSONAL',
      afecta_descuento: true,
      estado: 'PENDIENTE',
      creado_por: actorId,
    },
    result.permisos,
  );

  await ensurePermiso(
    prisma,
    empresaId,
    {
      empleado_id: empleados[2].id,
      fecha: fechaD2,
      tipo_permiso: 'SALIDA_ANTICIPADA',
      hora_desde: timeUtc(16, 0),
      hora_hasta: timeUtc(17, 0),
      motivo: `${SEED_TAG} salida anticipada rechazada`,
      tipo_justificacion: 'PERSONAL',
      afecta_descuento: true,
      estado: 'RECHAZADO',
      rechazado_por: actorId,
      fecha_rechazo: timestampUtc(fechaD1, 18, 0),
      creado_por: actorId,
    },
    result.permisos,
  );

  await ensurePermiso(
    prisma,
    empresaId,
    {
      empleado_id: empleados[3].id,
      fecha: fechaD1,
      tipo_permiso: 'TURNO',
      turno_bloque_orden: 1,
      hora_desde: timeUtc(7, 0),
      hora_hasta: timeUtc(11, 30),
      motivo: `${SEED_TAG} permiso de bloque aprobado`,
      tipo_justificacion: 'INSTITUCIONAL',
      afecta_descuento: false,
      estado: 'APROBADO',
      aprobado_por: actorId,
      fecha_aprobacion: timestampUtc(fechaD1, 6, 40),
      creado_por: actorId,
    },
    result.permisos,
  );

  const permiso5 = await ensurePermiso(
    prisma,
    empresaId,
    {
      empleado_id: empleados[4].id,
      fecha: fechaD2,
      tipo_permiso: 'AUSENCIA_TURNO',
      turno_bloque_orden: 2,
      motivo: `${SEED_TAG} ausencia de segundo bloque pendiente`,
      tipo_justificacion: 'FUERZA_MAYOR',
      afecta_descuento: true,
      estado: 'PENDIENTE',
      creado_por: actorId,
    },
    result.permisos,
  );

  async function ensureRaw(data: {
    reloj_id: string | null;
    fuente: 'API' | 'PLANILLA';
    documento_raw: string;
    empleado_id: string | null;
    timestamp_marcacion: Date;
    tipo_marcacion: 'ENTRADA' | 'SALIDA' | null;
    estado_importacion: 'PENDIENTE' | 'PROCESADO' | 'DUPLICADO' | 'ERROR';
    batch_importacion_id: string;
    error_descripcion?: string | null;
  }) {
    const existing = await prisma.rrhh_marcaciones_raw.findFirst({
      where: {
        empresa_id: empresaId,
        batch_importacion_id: data.batch_importacion_id,
        documento_raw: data.documento_raw,
        timestamp_marcacion: data.timestamp_marcacion,
      },
      select: { id: true },
    });
    if (existing) {
      result.marcaciones_raw.existentes += 1;
      return existing.id;
    }
    const created = await prisma.rrhh_marcaciones_raw.create({
      data: {
        empresa_id: empresaId,
        reloj_id: data.reloj_id,
        fuente: data.fuente,
        documento_raw: data.documento_raw,
        empleado_id: data.empleado_id,
        timestamp_marcacion: data.timestamp_marcacion,
        tipo_marcacion: data.tipo_marcacion,
        estado_importacion: data.estado_importacion,
        batch_importacion_id: data.batch_importacion_id,
        error_descripcion: data.error_descripcion ?? null,
        importado_por: actorId,
      },
      select: { id: true },
    });
    result.marcaciones_raw.creadas += 1;
    return created.id;
  }

  const rawEmp0Entrada = await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[0].cedula_identidad,
    empleado_id: empleados[0].id,
    timestamp_marcacion: timestampUtc(fechaD2, 8, 2),
    tipo_marcacion: 'ENTRADA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });
  await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[0].cedula_identidad,
    empleado_id: empleados[0].id,
    timestamp_marcacion: timestampUtc(fechaD2, 8, 4),
    tipo_marcacion: 'ENTRADA',
    estado_importacion: 'DUPLICADO',
    batch_importacion_id: batchDup,
    error_descripcion: `${SEED_TAG} duplicado de entrada`,
  });
  const rawEmp0Salida = await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[0].cedula_identidad,
    empleado_id: empleados[0].id,
    timestamp_marcacion: timestampUtc(fechaD2, 17, 1),
    tipo_marcacion: 'SALIDA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });

  const rawEmp1Entrada = await ensureRaw({
    reloj_id: relojPlanillaId,
    fuente: 'PLANILLA',
    documento_raw: empleados[1].cedula_identidad,
    empleado_id: empleados[1].id,
    timestamp_marcacion: timestampUtc(fechaD2, 8, 30),
    tipo_marcacion: 'ENTRADA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });
  const rawEmp1Salida = await ensureRaw({
    reloj_id: relojPlanillaId,
    fuente: 'PLANILLA',
    documento_raw: empleados[1].cedula_identidad,
    empleado_id: empleados[1].id,
    timestamp_marcacion: timestampUtc(fechaD2, 17, 0),
    tipo_marcacion: 'SALIDA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });

  const rawEmp2Entrada = await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[2].cedula_identidad,
    empleado_id: empleados[2].id,
    timestamp_marcacion: timestampUtc(fechaD2, 8, 0),
    tipo_marcacion: 'ENTRADA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });
  const rawEmp2Salida = await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[2].cedula_identidad,
    empleado_id: empleados[2].id,
    timestamp_marcacion: timestampUtc(fechaD2, 16, 20),
    tipo_marcacion: 'SALIDA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });

  const rawEmp3Entrada = await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[3].cedula_identidad,
    empleado_id: empleados[3].id,
    timestamp_marcacion: timestampUtc(fechaD2, 7, 5),
    tipo_marcacion: 'ENTRADA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });
  const rawEmp3Salida = await ensureRaw({
    reloj_id: relojApiId,
    fuente: 'API',
    documento_raw: empleados[3].cedula_identidad,
    empleado_id: empleados[3].id,
    timestamp_marcacion: timestampUtc(fechaD2, 11, 35),
    tipo_marcacion: 'SALIDA',
    estado_importacion: 'PROCESADO',
    batch_importacion_id: batchMain,
  });

  await ensureRaw({
    reloj_id: relojPlanillaId,
    fuente: 'PLANILLA',
    documento_raw: 'DOC_SEED_ERR_01',
    empleado_id: null,
    timestamp_marcacion: timestampUtc(fechaD2, 9, 0),
    tipo_marcacion: null,
    estado_importacion: 'ERROR',
    batch_importacion_id: batchErr,
    error_descripcion: `${SEED_TAG} documento no corresponde a empleado`,
  });

  async function upsertLimpia(data: {
    empleado_id: string;
    fecha: Date;
    hora_entrada: Date | null;
    hora_salida: Date | null;
    turno_id: string;
    procesado_novedades: boolean;
    marcacion_raw_entrada_id: string | null;
    marcacion_raw_salida_id: string | null;
  }) {
    const existing = await prisma.rrhh_marcaciones_limpias.findFirst({
      where: {
        empresa_id: empresaId,
        empleado_id: data.empleado_id,
        fecha: dateBetweenUtc(data.fecha),
      },
      select: { id: true },
    });
    if (existing) {
      await prisma.rrhh_marcaciones_limpias.update({
        where: { id: existing.id },
        data: {
          hora_entrada: data.hora_entrada,
          hora_salida: data.hora_salida,
          turno_id: data.turno_id,
          procesado_novedades: data.procesado_novedades,
          marcacion_raw_entrada_id: data.marcacion_raw_entrada_id,
          marcacion_raw_salida_id: data.marcacion_raw_salida_id,
          updated_at: new Date(),
        },
      });
      result.marcaciones_limpias.actualizadas += 1;
      return existing.id;
    }
    const created = await prisma.rrhh_marcaciones_limpias.create({
      data: {
        empresa_id: empresaId,
        empleado_id: data.empleado_id,
        fecha: startOfDayUtc(data.fecha),
        hora_entrada: data.hora_entrada,
        hora_salida: data.hora_salida,
        turno_id: data.turno_id,
        procesado_novedades: data.procesado_novedades,
        marcacion_raw_entrada_id: data.marcacion_raw_entrada_id,
        marcacion_raw_salida_id: data.marcacion_raw_salida_id,
      },
      select: { id: true },
    });
    result.marcaciones_limpias.creadas += 1;
    return created.id;
  }

  const limpiaEmp0 = await upsertLimpia({
    empleado_id: empleados[0].id,
    fecha: fechaD2,
    hora_entrada: timeUtc(8, 2),
    hora_salida: timeUtc(17, 1),
    turno_id: turnoSimpleId,
    procesado_novedades: true,
    marcacion_raw_entrada_id: rawEmp0Entrada,
    marcacion_raw_salida_id: rawEmp0Salida,
  });
  await upsertLimpia({
    empleado_id: empleados[1].id,
    fecha: fechaD2,
    hora_entrada: timeUtc(8, 30),
    hora_salida: timeUtc(17, 0),
    turno_id: turnoSimpleId,
    procesado_novedades: true,
    marcacion_raw_entrada_id: rawEmp1Entrada,
    marcacion_raw_salida_id: rawEmp1Salida,
  });
  const limpiaEmp2 = await upsertLimpia({
    empleado_id: empleados[2].id,
    fecha: fechaD2,
    hora_entrada: timeUtc(8, 0),
    hora_salida: timeUtc(16, 20),
    turno_id: turnoSimpleId,
    procesado_novedades: false,
    marcacion_raw_entrada_id: rawEmp2Entrada,
    marcacion_raw_salida_id: rawEmp2Salida,
  });
  await upsertLimpia({
    empleado_id: empleados[3].id,
    fecha: fechaD2,
    hora_entrada: timeUtc(7, 5),
    hora_salida: timeUtc(11, 35),
    turno_id: turnoCortadoId,
    procesado_novedades: true,
    marcacion_raw_entrada_id: rawEmp3Entrada,
    marcacion_raw_salida_id: rawEmp3Salida,
  });
  await upsertLimpia({
    empleado_id: empleados[4].id,
    fecha: fechaD2,
    hora_entrada: null,
    hora_salida: null,
    turno_id: turnoCortadoId,
    procesado_novedades: false,
    marcacion_raw_entrada_id: null,
    marcacion_raw_salida_id: null,
  });
  await upsertLimpia({
    empleado_id: empleados[5].id,
    fecha: fechaD1,
    hora_entrada: timeUtc(8, 3),
    hora_salida: timeUtc(17, 5),
    turno_id: turnoSimpleId,
    procesado_novedades: false,
    marcacion_raw_entrada_id: null,
    marcacion_raw_salida_id: null,
  });

  const existingLog = await prisma.rrhh_marcaciones_log_correcciones.findFirst({
    where: {
      empresa_id: empresaId,
      empleado_id: empleados[0].id,
      fecha: dateBetweenUtc(fechaD2),
      batch_importacion_id: batchDup,
    },
    select: { id: true },
  });
  if (existingLog) {
    result.log_correcciones.existentes += 1;
  } else {
    await prisma.rrhh_marcaciones_log_correcciones.create({
      data: {
        empresa_id: empresaId,
        empleado_id: empleados[0].id,
        fecha: startOfDayUtc(fechaD2),
        batch_importacion_id: batchDup,
        cantidad_marcaciones_raw: 3,
        cantidad_duplicados_descartados: 1,
        timestamp_entrada_seleccionado: timestampUtc(fechaD2, 8, 2),
        timestamp_salida_seleccionado: timestampUtc(fechaD2, 17, 1),
        timestamps_descartados: {
          descartados: [timestampUtc(fechaD2, 8, 4).toISOString()],
          motivo: `${SEED_TAG} deduplicación de entrada`,
        },
        motivo_correccion: 'DUPLICADO_ENTRADA',
        procesado_por: actorId,
      },
    });
    result.log_correcciones.creados += 1;
  }

  const existingAjuste = await prisma.rrhh_marcaciones_ajustes_manuales.findFirst({
    where: {
      empresa_id: empresaId,
      marcacion_limpia_id: limpiaEmp2,
      campo_modificado: 'HORA_SALIDA',
      motivo: `${SEED_TAG} ajuste manual de salida`,
    },
    select: { id: true },
  });
  if (existingAjuste) {
    result.ajustes_manuales.existentes += 1;
  } else {
    await prisma.rrhh_marcaciones_ajustes_manuales.create({
      data: {
        empresa_id: empresaId,
        marcacion_limpia_id: limpiaEmp2,
        campo_modificado: 'HORA_SALIDA',
        valor_anterior: '16:20',
        valor_nuevo: '16:35',
        motivo: `${SEED_TAG} ajuste manual de salida`,
        ajustado_por: actorId,
      },
    });
    await prisma.rrhh_marcaciones_limpias.update({
      where: { id: limpiaEmp2 },
      data: { hora_salida: timeUtc(16, 35), procesado_novedades: false, updated_at: new Date() },
    });
    result.ajustes_manuales.creados += 1;
  }

  const novedades = [
    {
      empleado_id: empleados[1].id,
      fecha: fechaD2,
      tipo_novedad: 'TARDANZA' as const,
      minutos_tardanza: 30,
      justificado: false,
      tipo_justificacion: null as string | null,
      impacta_liquidacion: true,
      observacion: `${SEED_TAG} tardanza por llegada 08:30`,
      turno_id: turnoSimpleId,
      turno_bloque_orden: 1,
      hora_esperada: timeUtc(8, 0),
      hora_real: timeUtc(8, 30),
      tolerancia_aplicada_min: 5,
      permiso_id: permiso2,
      generado_automatico: true,
      estado: 'CONFIRMADA' as const,
    },
    {
      empleado_id: empleados[2].id,
      fecha: fechaD2,
      tipo_novedad: 'SALIDA_ANTICIPADA' as const,
      minutos_tardanza: 25,
      justificado: false,
      tipo_justificacion: null as string | null,
      impacta_liquidacion: true,
      observacion: `${SEED_TAG} salida anticipada`,
      turno_id: turnoSimpleId,
      turno_bloque_orden: 1,
      hora_esperada: timeUtc(17, 0),
      hora_real: timeUtc(16, 35),
      tolerancia_aplicada_min: 5,
      permiso_id: null as string | null,
      generado_automatico: true,
      estado: 'CONFIRMADA' as const,
    },
    {
      empleado_id: empleados[4].id,
      fecha: fechaD2,
      tipo_novedad: 'AUSENCIA_BLOQUE' as const,
      minutos_tardanza: null,
      justificado: false,
      tipo_justificacion: null as string | null,
      impacta_liquidacion: true,
      observacion: `${SEED_TAG} ausencia en bloque 2`,
      turno_id: turnoCortadoId,
      turno_bloque_orden: 2,
      hora_esperada: timeUtc(13, 0),
      hora_real: null as Date | null,
      tolerancia_aplicada_min: 0,
      permiso_id: permiso5,
      generado_automatico: true,
      estado: 'CONFIRMADA' as const,
    },
    {
      empleado_id: empleados[5].id,
      fecha: fechaD3,
      tipo_novedad: 'AUSENCIA_JORNADA' as const,
      minutos_tardanza: null,
      justificado: false,
      tipo_justificacion: null as string | null,
      impacta_liquidacion: true,
      observacion: `${SEED_TAG} ausencia jornada completa`,
      turno_id: turnoSimpleId,
      turno_bloque_orden: null as number | null,
      hora_esperada: timeUtc(8, 0),
      hora_real: null as Date | null,
      tolerancia_aplicada_min: 0,
      permiso_id: null as string | null,
      generado_automatico: true,
      estado: 'CONFIRMADA' as const,
    },
    {
      empleado_id: empleados[0].id,
      fecha: fechaD1,
      tipo_novedad: 'AUSENCIA_JUSTIFICADA' as const,
      minutos_tardanza: null,
      justificado: true,
      tipo_justificacion: 'MEDICA',
      impacta_liquidacion: false,
      observacion: `${SEED_TAG} ausencia justificada por permiso`,
      turno_id: turnoSimpleId,
      turno_bloque_orden: null as number | null,
      hora_esperada: timeUtc(8, 0),
      hora_real: null as Date | null,
      tolerancia_aplicada_min: 0,
      permiso_id: permiso1,
      generado_automatico: true,
      estado: 'CONFIRMADA' as const,
    },
    {
      empleado_id: empleados[3].id,
      fecha: fechaD1,
      tipo_novedad: 'TARDANZA_JUSTIFICADA' as const,
      minutos_tardanza: 20,
      justificado: true,
      tipo_justificacion: 'INSTITUCIONAL',
      impacta_liquidacion: false,
      observacion: `${SEED_TAG} tardanza justificada`,
      turno_id: turnoCortadoId,
      turno_bloque_orden: 1,
      hora_esperada: timeUtc(7, 0),
      hora_real: timeUtc(7, 20),
      tolerancia_aplicada_min: 0,
      permiso_id: null as string | null,
      generado_automatico: false,
      estado: 'ANULADA' as const,
    },
  ];

  for (const nov of novedades) {
    const existing = await prisma.rrhh_asistencia_novedades.findFirst({
      where: {
        empresa_id: empresaId,
        empleado_id: nov.empleado_id,
        fecha: dateBetweenUtc(nov.fecha),
        tipo_novedad: nov.tipo_novedad,
        observacion: nov.observacion,
      },
      select: { id: true },
    });
    if (existing) {
      result.novedades.existentes += 1;
      continue;
    }
    await prisma.rrhh_asistencia_novedades.create({
      data: {
        empresa_id: empresaId,
        empleado_id: nov.empleado_id,
        fecha: startOfDayUtc(nov.fecha),
        tipo_novedad: nov.tipo_novedad,
        hora_entrada_real: null,
        hora_entrada_esperada: null,
        minutos_tardanza: nov.minutos_tardanza,
        justificado: nov.justificado,
        tipo_justificacion: nov.tipo_justificacion,
        impacta_liquidacion: nov.impacta_liquidacion,
        observacion: nov.observacion,
        creado_por: actorId,
        turno_id: nov.turno_id,
        turno_bloque_orden: nov.turno_bloque_orden,
        hora_esperada: nov.hora_esperada,
        hora_real: nov.hora_real,
        tolerancia_aplicada_min: nov.tolerancia_aplicada_min,
        permiso_id: nov.permiso_id,
        generado_automatico: nov.generado_automatico,
        estado: nov.estado,
      },
    });
    result.novedades.creadas += 1;
  }

  // Reasegura marca de deduplicación para el caso que usa ajustes/log.
  await prisma.rrhh_marcaciones_limpias.update({
    where: { id: limpiaEmp0 },
    data: { procesado_novedades: true, updated_at: new Date() },
  });

  return result;
}
