import { and, eq, sql } from 'drizzle-orm';
import { NextRequest, NextResponse } from 'next/server';
import * as XLSX from 'xlsx';
import { departments, importJobs, organizations, positions, trainingRecords } from '@/db/schema';
import {
  normalizeImportedStatus,
  thaiImportEmployeeHeaders,
  thaiImportTemplateHeaders,
  thaiImportTrainingHeaders,
  thaiTrainingTypeLabels
} from '@/constants/thai-labels';
import {
  AUDIT_ACTION,
  AUDIT_MODULE,
  getRequestMetadata,
  logAuditEvent,
  toAuditValue
} from '@/features/audit-logs/server/audit-service';
import {
  findEmployeeByCodeForOrganization,
  syncUsersForEmployeeCode,
  upsertEmployeeMasterFromImport
} from '@/features/employees/server/employee-identity-linking';
import { createImportCompletedNotification } from '@/features/notifications/server/notification-service';
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 { AuthError, requireHRD } from '@/lib/auth/session';
import { db } from '@/lib/db';

type ImportedTrainingRecordInput = {
  courseName: string;
  category: 'K' | 'S' | 'A';
  hours: number;
  trainingType: LegacyTrainingType;
  trainingDate: Date;
};

type EmployeeImportGroup = {
  employeeCode: string;
  employeeName: string;
  email: string;
  company: string;
  department: string;
  position: string;
  status: string;
  rowNumbers: number[];
  trainingRecords: Array<{
    rowNumber: number;
    data: ImportedTrainingRecordInput;
  }>;
};

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

const EMPLOYEE_HEADERS = {
  employeeCode: thaiImportEmployeeHeaders[0],
  employeeName: thaiImportEmployeeHeaders[1],
  email: thaiImportEmployeeHeaders[2],
  company: thaiImportEmployeeHeaders[3],
  department: thaiImportEmployeeHeaders[4],
  position: thaiImportEmployeeHeaders[5],
  status: thaiImportEmployeeHeaders[6]
} as const;

const TRAINING_HEADERS = {
  courseName: thaiImportTrainingHeaders[0],
  category: thaiImportTrainingHeaders[1],
  hours: thaiImportTrainingHeaders[2],
  trainingType: thaiImportTrainingHeaders[3],
  trainingDate: thaiImportTrainingHeaders[4]
} as const;

const allowedTrainingTypeKeys = new Set([
  'INTERNAL_TRAINING',
  'EXTERNAL_TRAINING',
  'ONLINE_COURSE',
  'OJT',
  'internal',
  'external',
  'online',
  'onsite'
]);

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

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

function normalizeComparableText(value: string) {
  return value.trim().toLowerCase();
}

function parseImportYear(value: FormDataEntryValue | null) {
  const currentYear = new Date().getUTCFullYear();
  const rawValue = typeof value === 'string' ? value.trim() : '';

  if (!rawValue) {
    return currentYear;
  }

  const parsedYear = Number(rawValue);
  if (!Number.isFinite(parsedYear)) {
    return currentYear;
  }

  return parsedYear >= 2400 ? parsedYear - 543 : parsedYear;
}

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 normalizeImportedCalendarYear(year: number) {
  return year >= 2400 ? year - 543 : year;
}

function createDefaultTrainingDate(importYear: number) {
  return new Date(Date.UTC(importYear, 0, 1));
}

function parseImportedTrainingDate(value: unknown, importYear: number) {
  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 createDefaultTrainingDate(importYear);
  }

  const isoMatch = normalized.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/);
  if (isoMatch) {
    const year = normalizeImportedCalendarYear(Number(isoMatch[1]));
    const month = Number(isoMatch[2]);
    const day = Number(isoMatch[3]);
    const parsedDate = new Date(Date.UTC(year, month - 1, day));

    if (Number.isFinite(parsedDate.getTime())) {
      return parsedDate;
    }
  }

  const localMatch = normalized.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{2,4})$/);
  if (localMatch) {
    const day = Number(localMatch[1]);
    const month = Number(localMatch[2]);
    const rawYear = Number(localMatch[3]);
    const year = normalizeImportedCalendarYear(rawYear < 100 ? rawYear + 2000 : rawYear);
    const parsedDate = new Date(Date.UTC(year, month - 1, day));

    if (Number.isFinite(parsedDate.getTime())) {
      return parsedDate;
    }
  }

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

  return null;
}

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

  if (!normalized) {
    return 'internal' as const;
  }

  if (allowedTrainingTypeKeys.has(normalized)) {
    return normalizeTrainingTypeForDb(normalized);
  }

  const matchedLabelEntry = Object.entries(thaiTrainingTypeLabels).find(
    ([key, label]) =>
      allowedTrainingTypeKeys.has(key) &&
      normalizeComparableText(label) === normalizeComparableText(normalized)
  );

  if (matchedLabelEntry) {
    return normalizeTrainingTypeForDb(matchedLabelEntry[0]);
  }

  const comparable = normalizeComparableText(normalized).replaceAll('-', ' ').replaceAll('_', ' ');

  if (comparable.includes('online')) {
    return 'online';
  }

  if (comparable.includes('ojt') || comparable.includes('on the job')) {
    return 'onsite';
  }

  if (comparable.includes('external') || comparable.includes('public')) {
    return 'external';
  }

  if (comparable.includes('internal')) {
    return 'internal';
  }

  return null;
}

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

  if (!normalized) {
    return null;
  }

  const uppercased = normalized.toUpperCase();

  if (uppercased === 'K' || uppercased.startsWith('KNOWLEDGE')) {
    return 'K' as const;
  }

  if (uppercased === 'S' || uppercased.startsWith('SKILL')) {
    return 'S' as const;
  }

  if (uppercased === 'A' || uppercased.startsWith('ATTITUDE')) {
    return 'A' as const;
  }

  return null;
}

function validateImportedTrainingRecord(
  rowNumber: number,
  row: Record<string, unknown>,
  employeeCode: string,
  importYear: number
) {
  const courseName = normalizeText(row[TRAINING_HEADERS.courseName]);
  const rawCategory = row[TRAINING_HEADERS.category];
  const rawHours = row[TRAINING_HEADERS.hours];
  const rawTrainingType = row[TRAINING_HEADERS.trainingType];
  const rawTrainingDate = row[TRAINING_HEADERS.trainingDate];
  const hasAnyTrainingValue = [courseName, rawCategory, rawHours, rawTrainingType, rawTrainingDate].some(
    (value) => normalizeText(value).length > 0
  );

  if (!hasAnyTrainingValue) {
    return { trainingRecord: null };
  }

  if (!courseName) {
    return { error: rowError(rowNumber, 'กรุณาระบุชื่อหลักสูตร', employeeCode) };
  }

  const category = parseImportedCategory(rawCategory);

  if (!category) {
    return {
      error: rowError(rowNumber, 'หมวดหมู่ต้องเป็น K, S หรือ A', employeeCode)
    };
  }

  const hours = parseOptionalNumber(rawHours);

  if (hours === null) {
    return { error: rowError(rowNumber, 'กรุณาระบุชั่วโมงอบรม', employeeCode) };
  }

  if (Number.isNaN(hours)) {
    return { error: rowError(rowNumber, 'ชั่วโมงอบรมต้องเป็นตัวเลข', employeeCode) };
  }

  if ((hours ?? 0) <= 0) {
    return { error: rowError(rowNumber, 'ชั่วโมงอบรมต้องมากกว่า 0', employeeCode) };
  }

  const trainingType = parseImportedTrainingType(rawTrainingType);

  if (!trainingType) {
    return {
      error: rowError(
        rowNumber,
        'ประเภทการอบรมต้องเป็น internal, external, online หรือ OJT',
        employeeCode
      )
    };
  }

  const trainingDate = parseImportedTrainingDate(rawTrainingDate, importYear);

  if (!trainingDate) {
    return { error: rowError(rowNumber, 'วันที่อบรมไม่ถูกต้อง', employeeCode) };
  }

  return {
    trainingRecord: {
      courseName,
      category,
      hours: Number(hours.toFixed(2)),
      trainingType,
      trainingDate
    } satisfies ImportedTrainingRecordInput
  };
}

async function resolvePendingDepartmentId(organizationId: string, name: string) {
  const existing = await db.query.departments.findFirst({
    where: and(eq(departments.organizationId, organizationId), eq(departments.name, name))
  });

  if (existing) {
    return { id: existing.id, pending: existing.pendingMasterReview };
  }

  const [department] = await db
    .insert(departments)
    .values({
      organizationId,
      name,
      isActive: false,
      pendingMasterReview: true
    })
    .returning({ id: departments.id });

  return { id: department.id, pending: true };
}

async function resolvePendingPositionId(organizationId: string, name: string) {
  const existing = await db.query.positions.findFirst({
    where: and(eq(positions.organizationId, organizationId), eq(positions.name, name))
  });

  if (existing) {
    return { id: existing.id, pending: existing.pendingMasterReview };
  }

  const [position] = await db
    .insert(positions)
    .values({
      organizationId,
      name,
      isActive: false,
      pendingMasterReview: true
    })
    .returning({ id: positions.id });

  return { id: position.id, pending: true };
}

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

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

function hasMatchingEmployeeIdentity(
  group: EmployeeImportGroup,
  row: Omit<EmployeeImportGroup, 'rowNumbers' | 'trainingRecords'>
) {
  return (
    group.employeeName === row.employeeName &&
    group.email === row.email &&
    group.company === row.company &&
    group.department === row.department &&
    group.position === row.position &&
    group.status === row.status
  );
}

async function insertImportedTrainingRecord({
  organizationId,
  userId,
  employeeId,
  sessionUserId,
  trainingRecord
}: {
  organizationId: string;
  userId: string | null;
  employeeId: number;
  sessionUserId: string;
  trainingRecord: ImportedTrainingRecordInput;
}) {
  const minutes = decimalHoursToMinutes(trainingRecord.hours);
  const baseValues = {
    organizationId,
    userId,
    employeeId,
    courseId: null,
    courseName: trainingRecord.courseName,
    trainingDate: trainingRecord.trainingDate,
    trainingType: trainingRecord.trainingType,
    hours: trainingRecord.hours,
    approvedHours: trainingRecord.hours,
    category: trainingRecord.category,
    organizer: null,
    note: 'imported from employee excel',
    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 requireHRD();
    const requestMetadata = getRequestMetadata(request);
    const formData = await request.formData();
    const file = formData.get('file');
    const importYear = parseImportYear(formData.get('year'));

    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: `หัวตารางไม่ถูกต้อง กรุณาใช้เทมเพลตภาษาไทยของระบบ: ${thaiImportTemplateHeaders.join(', ')}`
        },
        { status: 400 }
      );
    }

    const errors: ImportError[] = [];
    const groupedRows = new Map<string, EmployeeImportGroup>();
    const emailOwners = new Map<string, string>();

    for (const [index, row] of rows.entries()) {
      const rowNumber = index + 2;
      const employeeCode = normalizeText(row[EMPLOYEE_HEADERS.employeeCode]);
      const employeeName = normalizeText(row[EMPLOYEE_HEADERS.employeeName]);
      const email = normalizeText(row[EMPLOYEE_HEADERS.email]).toLowerCase();
      const company = normalizeText(row[EMPLOYEE_HEADERS.company]);
      const department = normalizeText(row[EMPLOYEE_HEADERS.department]);
      const position = normalizeText(row[EMPLOYEE_HEADERS.position]);
      const status = normalizeText(row[EMPLOYEE_HEADERS.status]);

      if (!employeeCode || !employeeName || !company || !department || !position || !status) {
        errors.push(
          rowError(rowNumber, 'กรุณากรอกข้อมูลที่จำเป็นให้ครบทุกช่อง', employeeCode)
        );
        continue;
      }

      if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
        errors.push(rowError(rowNumber, 'รูปแบบอีเมลไม่ถูกต้อง', employeeCode));
        continue;
      }

      if (normalizeImportedStatus(status) === null) {
        errors.push(rowError(rowNumber, 'สถานะต้องเป็น ใช้งาน หรือ ไม่ใช้งาน', employeeCode));
        continue;
      }

      const trainingValidation = validateImportedTrainingRecord(
        rowNumber,
        row,
        employeeCode,
        importYear
      );
      if ('error' in trainingValidation && trainingValidation.error) {
        errors.push(trainingValidation.error);
        continue;
      }

      if (email) {
        const ownedByEmployeeCode = emailOwners.get(email);

        if (ownedByEmployeeCode && ownedByEmployeeCode !== employeeCode) {
          errors.push(rowError(rowNumber, 'พบอีเมลซ้ำกับพนักงานอีกคนในไฟล์นำเข้า', employeeCode));
          continue;
        }

        emailOwners.set(email, employeeCode);
      }

      const existingGroup = groupedRows.get(employeeCode);
      const identityRow = {
        employeeCode,
        employeeName,
        email,
        company,
        department,
        position,
        status
      };

      if (existingGroup) {
        if (!hasMatchingEmployeeIdentity(existingGroup, identityRow)) {
          errors.push(
            rowError(rowNumber, 'พบรหัสพนักงานซ้ำแต่ข้อมูลพนักงานไม่ตรงกัน', employeeCode)
          );
          continue;
        }

        existingGroup.rowNumbers.push(rowNumber);

        if (trainingValidation.trainingRecord) {
          existingGroup.trainingRecords.push({
            rowNumber,
            data: trainingValidation.trainingRecord
          });
        }

        continue;
      }

      groupedRows.set(employeeCode, {
        ...identityRow,
        rowNumbers: [rowNumber],
        trainingRecords: trainingValidation.trainingRecord
          ? [
              {
                rowNumber,
                data: trainingValidation.trainingRecord
              }
            ]
          : []
      });
    }

    let createdEmployees = 0;
    let updatedEmployees = 0;
    let linkedUsers = 0;
    let importedTrainingRecords = 0;
    const pendingCompanyNames = new Set<string>();

    for (const group of groupedRows.values()) {
      try {
        const isActive = normalizeImportedStatus(group.status);

        if (isActive === null) {
          for (const rowNumber of group.rowNumbers) {
            errors.push(rowError(rowNumber, 'สถานะไม่ถูกต้อง', group.employeeCode));
          }
          continue;
        }

        const [departmentResult, positionResult, existingEmployee] = await Promise.all([
          resolvePendingDepartmentId(organization.id, group.department),
          resolvePendingPositionId(organization.id, group.position),
          findEmployeeByCodeForOrganization({
            organizationId: organization.id,
            employeeCode: group.employeeCode
          })
        ]);

        if (group.company !== organization.name) {
          pendingCompanyNames.add(group.company);
        }

        const employee = await upsertEmployeeMasterFromImport({
          organizationId: organization.id,
          employeeCode: group.employeeCode,
          fullName: group.employeeName,
          email: group.email,
          companyName: group.company,
          departmentId: departmentResult.id,
          positionId: positionResult.id,
          isActive
        });

        if (existingEmployee) {
          updatedEmployees += 1;
        } else {
          createdEmployees += 1;
        }

        const syncedUserIds = await syncUsersForEmployeeCode({
          organizationId: organization.id,
          employeeCode: group.employeeCode,
          employeeId: employee.id
        });
        linkedUsers += syncedUserIds.length;

        for (const trainingRecordRow of group.trainingRecords) {
          try {
            await insertImportedTrainingRecord({
              organizationId: organization.id,
              userId: syncedUserIds[0] ?? null,
              employeeId: employee.id,
              sessionUserId: session.user.id,
              trainingRecord: trainingRecordRow.data
            });

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

    const pendingCompanies = pendingCompanyNames.size;

    await db
      .update(organizations)
      .set({
        pendingMasterReview: pendingCompanies > 0,
        updatedAt: new Date()
      })
      .where(eq(organizations.id, organization.id));

    const [pendingDepartmentsRow] = await db
      .select({ value: sql<number>`count(*)::int` })
      .from(departments)
      .where(
        and(eq(departments.organizationId, organization.id), eq(departments.pendingMasterReview, true))
      );

    const [pendingPositionsRow] = await db
      .select({ value: sql<number>`count(*)::int` })
      .from(positions)
      .where(and(eq(positions.organizationId, organization.id), eq(positions.pendingMasterReview, true)));

    const failedRows = errors.length;
    const successRows = Math.max(rows.length - failedRows, 0);
    const summary = {
      total_rows: rows.length,
      created_employees: createdEmployees,
      updated_employees: updatedEmployees,
      linked_users: linkedUsers,
      imported_training_records: importedTrainingRecords,
      failed_rows: failedRows,
      pending_companies: pendingCompanies,
      pending_departments: pendingDepartmentsRow?.value ?? 0,
      pending_positions: pendingPositionsRow?.value ?? 0
    };

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

    await createImportCompletedNotification({
      organizationId: organization.id,
      userId: session.user.id,
      totalRows: rows.length,
      successRows,
      failedRows,
      referenceId: job.id
    });

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

    return NextResponse.json({
      success: true,
      message:
        failedRows === 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 }
    );
  }
}
