import { and, eq } from 'drizzle-orm';
import { NextRequest, NextResponse } from 'next/server';
import * as XLSX from 'xlsx';
import { importJobs, userEmployeeMaps } from '@/db/schema';
import {
  AUDIT_ACTION,
  AUDIT_MODULE,
  getRequestMetadata,
  logAuditEvent,
  toAuditValue
} from '@/features/audit-logs/server/audit-service';
import {
  findEmployeeByCodeForOrganization,
  syncUsersForEmployeeCode
} from '@/features/employees/server/employee-identity-linking';
import {
  normalizeTrainingTypeForDb,
  type LegacyTrainingType
} from '@/features/training-records/constants/training-record-options';
import { isMissingTrainingRecordMinutesStorage } from '@/features/training-records/server/training-record-data';
import { decimalHoursToMinutes } from '@/features/training-records/utils/time-utils';
import { trainingRecords } from '@/db/schema';
import { AuthError, requireAllPermissions } from '@/lib/auth/session';
import { db } from '@/lib/db';
import {
  trainingRecordImportCategoryValues,
  trainingRecordImportTemplateHeaders,
  trainingRecordImportTrainingTypeValues
} from '@/features/training-records/constants/import-training-records';

type TrainingRecordImportError = {
  row: number;
  employee_code?: string;
  message: string;
};

type ParsedImportRow = {
  rowNumber: number;
  employeeCode: string;
  courseName: string;
  trainingDate: Date;
  trainingType: LegacyTrainingType;
  locationType: string | null;
  hours: number;
  category: 'K' | 'S' | 'A';
  provider: string | null;
  note: string | null;
};

const HEADERS = {
  employeeCode: trainingRecordImportTemplateHeaders[0],
  courseName: trainingRecordImportTemplateHeaders[1],
  trainingDate: trainingRecordImportTemplateHeaders[2],
  trainingType: trainingRecordImportTemplateHeaders[3],
  locationType: trainingRecordImportTemplateHeaders[4],
  hours: trainingRecordImportTemplateHeaders[5],
  category: trainingRecordImportTemplateHeaders[6],
  provider: trainingRecordImportTemplateHeaders[7],
  note: trainingRecordImportTemplateHeaders[8]
} as const;

const allowedTrainingTypeKeys = new Set(trainingRecordImportTrainingTypeValues);
const allowedCategoryValues = new Set(trainingRecordImportCategoryValues);

function rowError(row: number, message: string, employeeCode?: string): TrainingRecordImportError {
  return { row, message, employee_code: employeeCode };
}

function normalizeText(value: unknown) {
  return String(value ?? '').trim();
}

function parseOptionalNumber(value: unknown) {
  const normalized = normalizeText(value);

  if (!normalized) {
    return null;
  }

  const parsed = Number(normalized.replaceAll(',', ''));
  return Number.isFinite(parsed) ? parsed : Number.NaN;
}

function parseDateValue(value: unknown) {
  if (value instanceof Date && Number.isFinite(value.getTime())) {
    return new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate()));
  }

  if (typeof value === 'number' && Number.isFinite(value)) {
    const parsed = XLSX.SSF.parse_date_code(value);

    if (parsed) {
      return new Date(Date.UTC(parsed.y, parsed.m - 1, parsed.d));
    }
  }

  const normalized = normalizeText(value);
  if (!normalized) {
    return null;
  }

  const parsedByRuntime = new Date(normalized);
  if (Number.isFinite(parsedByRuntime.getTime())) {
    return new Date(
      Date.UTC(
        parsedByRuntime.getUTCFullYear(),
        parsedByRuntime.getUTCMonth(),
        parsedByRuntime.getUTCDate()
      )
    );
  }

  return null;
}

function parseTrainingType(value: unknown) {
  const normalized = normalizeText(value);

  if (!normalized) {
    return null;
  }

  if (!allowedTrainingTypeKeys.has(normalized as (typeof trainingRecordImportTrainingTypeValues)[number])) {
    return null;
  }

  return normalizeTrainingTypeForDb(normalized);
}

function parseCategory(value: unknown) {
  const normalized = normalizeText(value).toUpperCase();

  if (!normalized) {
    return null;
  }

  if (!allowedCategoryValues.has(normalized as 'K' | 'S' | 'A')) {
    return null;
  }

  return normalized as 'K' | 'S' | 'A';
}

function validateHeaders(rows: Record<string, unknown>[]) {
  const availableHeaders = rows[0] ? Object.keys(rows[0]) : [];

  return trainingRecordImportTemplateHeaders.every((header) => availableHeaders.includes(header));
}

function buildImportedNote({
  locationType,
  note
}: {
  locationType: string | null;
  note: string | null;
}) {
  const parts = [
    locationType ? `location_type: ${locationType}` : null,
    note?.trim() ? note.trim() : null
  ].filter(Boolean);

  return parts.length ? parts.join('\n') : null;
}

async function insertApprovedTrainingRecord({
  organizationId,
  employeeId,
  userId,
  sessionUserId,
  row
}: {
  organizationId: string;
  employeeId: number;
  userId: string | null;
  sessionUserId: string;
  row: ParsedImportRow;
}) {
  const minutes = decimalHoursToMinutes(row.hours);
  const baseValues = {
    organizationId,
    userId,
    employeeId,
    courseId: null,
    courseName: row.courseName,
    trainingDate: row.trainingDate,
    trainingType: row.trainingType,
    hours: row.hours,
    approvedHours: row.hours,
    category: row.category,
    organizer: row.provider,
    note: buildImportedNote({
      locationType: row.locationType,
      note: row.note
    }),
    approvalStatus: 'approved' as const,
    submittedByUserId: sessionUserId,
    reviewedByUserId: sessionUserId,
    reviewedBy: sessionUserId,
    reviewedAt: new Date(),
    createdBy: sessionUserId
  };

  try {
    await db.insert(trainingRecords).values({
      ...baseValues,
      submittedMinutes: minutes,
      approvedMinutes: minutes
    });
  } catch (error) {
    if (!isMissingTrainingRecordMinutesStorage(error)) {
      throw error;
    }

    await db.insert(trainingRecords).values(baseValues);
  }
}

export async function POST(request: NextRequest) {
  try {
    const { organization, session } = await requireAllPermissions([
      'training_record:write_all',
      'training_record:approve'
    ]);
    const requestMetadata = getRequestMetadata(request);
    const formData = await request.formData();
    const file = formData.get('file');

    if (!(file instanceof File)) {
      return NextResponse.json({ message: 'กรุณาแนบไฟล์ Excel สำหรับนำเข้า' }, { status: 400 });
    }

    if (!file.name.toLowerCase().endsWith('.xlsx')) {
      return NextResponse.json({ message: 'ระบบรองรับเฉพาะไฟล์ .xlsx เท่านั้น' }, { status: 400 });
    }

    const buffer = await file.arrayBuffer();
    const workbook = XLSX.read(buffer, { type: 'array' });
    const worksheet = workbook.Sheets[workbook.SheetNames[0]];

    if (!worksheet) {
      return NextResponse.json({ message: 'ไม่พบข้อมูลในไฟล์ที่อัปโหลด' }, { status: 400 });
    }

    const rows = XLSX.utils.sheet_to_json<Record<string, unknown>>(worksheet, { defval: '' });

    if (rows.length === 0) {
      return NextResponse.json({ message: 'ไม่พบข้อมูลประวัติการอบรมในไฟล์ที่อัปโหลด' }, { status: 400 });
    }

    if (!validateHeaders(rows)) {
      return NextResponse.json(
        {
          message: `หัวตารางไม่ถูกต้อง กรุณาใช้ Template นี้: ${trainingRecordImportTemplateHeaders.join(', ')}`
        },
        { status: 400 }
      );
    }

    const errors: TrainingRecordImportError[] = [];
    const validRows: ParsedImportRow[] = [];

    for (const [index, row] of rows.entries()) {
      const rowNumber = index + 2;
      const employeeCode = normalizeText(row[HEADERS.employeeCode]).toUpperCase();
      const courseName = normalizeText(row[HEADERS.courseName]);
      const hours = parseOptionalNumber(row[HEADERS.hours]);
      const category = parseCategory(row[HEADERS.category]);
      const trainingType = parseTrainingType(row[HEADERS.trainingType]);
      const trainingDate = parseDateValue(row[HEADERS.trainingDate]);
      const locationType = normalizeText(row[HEADERS.locationType]) || null;
      const provider = normalizeText(row[HEADERS.provider]) || null;
      const note = normalizeText(row[HEADERS.note]) || null;

      if (!employeeCode) {
        errors.push(rowError(rowNumber, 'กรุณาระบุ employee_code'));
        continue;
      }

      if (!courseName) {
        errors.push(rowError(rowNumber, 'course_name ต้องไม่ว่าง', employeeCode));
        continue;
      }

      if (!trainingDate) {
        errors.push(rowError(rowNumber, 'training_date ต้องเป็นวันที่ที่ถูกต้อง', employeeCode));
        continue;
      }

      if (hours === null) {
        errors.push(rowError(rowNumber, 'กรุณาระบุ hours', employeeCode));
        continue;
      }

      if (Number.isNaN(hours) || hours <= 0) {
        errors.push(rowError(rowNumber, 'hours ต้องมากกว่า 0', employeeCode));
        continue;
      }

      if (!category) {
        errors.push(rowError(rowNumber, 'category ต้องเป็น K, S หรือ A', employeeCode));
        continue;
      }

      if (!trainingType) {
        errors.push(
          rowError(
            rowNumber,
            'training_type ต้องเป็น INTERNAL_TRAINING, EXTERNAL_TRAINING, ONLINE_COURSE หรือ OJT',
            employeeCode
          )
        );
        continue;
      }

      validRows.push({
        rowNumber,
        employeeCode,
        courseName,
        trainingDate,
        trainingType,
        locationType,
        hours: Number(hours.toFixed(2)),
        category,
        provider,
        note
      });
    }

    let importedRecords = 0;

    for (const row of validRows) {
      try {
        const employee = await findEmployeeByCodeForOrganization({
          organizationId: organization.id,
          employeeCode: row.employeeCode
        });

        if (!employee) {
          errors.push(rowError(row.rowNumber, 'employee_code ต้องมีอยู่ในระบบ', row.employeeCode));
          continue;
        }

        const syncedUserIds = await syncUsersForEmployeeCode({
          organizationId: organization.id,
          employeeCode: row.employeeCode,
          employeeId: employee.id
        });

        let trainingRecordUserId = syncedUserIds[0] ?? null;

        if (!trainingRecordUserId) {
          const [mappedUser] = await db
            .select({ userId: userEmployeeMaps.userId })
            .from(userEmployeeMaps)
            .where(
              and(
                eq(userEmployeeMaps.organizationId, organization.id),
                eq(userEmployeeMaps.employeeId, employee.id)
              )
            )
            .limit(1);

          trainingRecordUserId = mappedUser?.userId ?? null;
        }

        await insertApprovedTrainingRecord({
          organizationId: organization.id,
          employeeId: employee.id,
          userId: trainingRecordUserId,
          sessionUserId: session.user.id,
          row
        });

        importedRecords += 1;
      } catch (error) {
        errors.push(
          rowError(
            row.rowNumber,
            error instanceof Error
              ? error.message
              : 'เกิดข้อผิดพลาดระหว่างบันทึกประวัติการอบรม',
            row.employeeCode
          )
        );
      }
    }

    const summary = {
      total_rows: rows.length,
      imported_records: importedRecords,
      failed_rows: errors.length
    };

    const [job] = await db
      .insert(importJobs)
      .values({
        organizationId: organization.id,
        importType: 'training_record_excel',
        fileName: file.name,
        totalRows: rows.length,
        successRows: importedRecords,
        failedRows: errors.length,
        status: 'completed',
        createdBy: session.user.id
      })
      .returning({ id: importJobs.id });

    await logAuditEvent({
      organizationId: organization.id,
      userId: session.user.id,
      module: AUDIT_MODULE.TRAINING_RECORDS,
      action: AUDIT_ACTION.IMPORT,
      entityName: file.name,
      entityId: job.id,
      newValue: toAuditValue({
        file_name: file.name,
        summary
      }),
      ...requestMetadata
    });

    return NextResponse.json({
      success: true,
      message:
        errors.length === 0
          ? 'นำเข้าประวัติการอบรมเรียบร้อยแล้ว'
          : 'นำเข้าประวัติการอบรมสำเร็จบางส่วน กรุณาตรวจสอบรายการที่ผิดพลาด',
      import_job_id: job.id,
      summary,
      errors
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return NextResponse.json({ message: error.message }, { status: error.status });
    }

    return NextResponse.json({ message: 'ไม่สามารถนำเข้าประวัติการอบรมได้' }, { status: 500 });
  }
}
