/* eslint-disable @typescript-eslint/no-explicit-any */
import { UnitJobType } from '@/jobs/units/unit.job.js';
import UnitJob from '@/modules/project/unit/job/unit-job.model.js';
import Unit from '@/modules/project/unit/unit.model.js';
import { CustomFields } from '@/modules/customFields/customFields.model.js';
import { createWorker } from '@/shared/config/bullmq.config.js';
import { ApiError } from '@/shared/utils/errors/index.js';
import { defaultStatus } from '@/shared/utils/responseCode/httpStatusAlias.js';
import responseCodes from '@/shared/utils/responseCode/responseCode.js';
import ExcelJS from 'exceljs';
import fs from 'fs';
import Project from '@/modules/project/project.model.js';
import { getObjectId } from '@/shared/utils/commonHelper.js';

interface BulkUploadData {
  filePath: string;
  fileName: string;
  projectId: string;
  propertyTypeId: string;
  userId: string;
  floor: string;
  block: string;
  companyId?: string;
}

const { ProjectResponseCodes } = responseCodes;

async function processBulkUpload(
  data: BulkUploadData,
  job: any,
): Promise<void> {
  const {
    filePath,
    projectId,
    fileName,
    propertyTypeId,
    userId,
    floor,
    block,
  } = data;

  let unitJob = await UnitJob.findOne({ jobId: job.id });

  if (!unitJob)
    unitJob = await UnitJob.create({
      block: block,
      floor: floor,
      project: projectId,
      jobId: job.id,
      status: 'processing',
      progress: 0,
      filePath,
      fileName,
      propertyTypeId,
      createdBy: userId,
      totalUnits: 0,
      processedUnits: 0,
      successfulUnits: 0,
      failedUnits: 0,
      errorRecords: [],
    });
  else
    unitJob = await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      { status: 'processing', progress: 0 },
      { new: true },
    );

  try {
    await job.updateProgress(10);
    await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      { progress: 10 },
      { new: true },
    );

    if (!fs.existsSync(filePath)) {
      await UnitJob.findOneAndUpdate(
        { jobId: job.id },
        {
          status: 'failed',
          errorRecords: [{ row: 0, message: 'File not found' }],
        },
        { new: true },
      );
      throw new ApiError(
        defaultStatus.OK,
        'File not found',
        true,
        '',
        ProjectResponseCodes.PROJECT_ERROR,
      );
    }

    const workbook = new ExcelJS.Workbook();
    await workbook.xlsx.readFile(filePath);
    const worksheet = workbook.getWorksheet(1);

    if (!worksheet) {
      await UnitJob.findOneAndUpdate(
        { jobId: job.id },
        {
          status: 'failed',
          errorRecords: [{ row: 0, message: 'Invalid Excel file format' }],
        },
        { new: true },
      );
      throw new ApiError(
        defaultStatus.OK,
        'Invalid Excel file format',
        true,
        '',
        ProjectResponseCodes.PROJECT_ERROR,
      );
    }

    await job.updateProgress(30);
    await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      { progress: 30 },
      { new: true },
    );

    // Determine project's layout mode to parse columns accordingly
    const project = await Project.findById(projectId)
      .select('hasBlockLayout')
      .lean();
    if (!project)
      throw new ApiError(
        defaultStatus.OK,
        'Invalid projectId',
        true,
        '',
        ProjectResponseCodes.PROJECT_ERROR,
      );
    const hasBlockLayout = !!project.hasBlockLayout;

    // Fetch custom field definitions for unit form
    const customFieldQuery: Record<string, unknown> = {
      formName: 'unit',
      isDeleted: false,
    };
    
    // If companyId is provided in data, filter by it
    if (data.companyId) {
      customFieldQuery.companyId = getObjectId(data.companyId);
    }

    const customFieldDefs = await CustomFields.find(customFieldQuery).lean();

    // Create a map of column headers to custom field keys
    const customFieldColumnMap: Record<string, string> = {};
    customFieldDefs.forEach((field) => {
      // Use the field label as the column header
      customFieldColumnMap[field.label.toLowerCase()] = field.key;
    });

    const units = [];
    const errors = [];
    let rowNumber = 2;
    let totalRows = 0;

    worksheet.eachRow({ includeEmpty: false }, (_row, rowIndex) => {
      if (rowIndex > 1) totalRows++;
    });

    await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      { totalUnits: totalRows },
      { new: true },
    );

    // Get column headers from first row
    const headerRow = worksheet.getRow(1);
    const columnHeaders: string[] = [];
    headerRow.eachCell({ includeEmpty: false }, (cell, colNumber) => {
      columnHeaders[colNumber - 1] = cell.text.toLowerCase().trim();
    });

    worksheet.eachRow({ includeEmpty: false }, (row, rowIndex) => {
      if (rowIndex === 1) return;

      try {
        let blockValue: string | null = null;
        let floorValue: number | null = null;
        let unitNumber: string;
        let size: number;
        let price: number;
        let status: string;
        const customFields: Record<string, unknown> = {};

        if (hasBlockLayout) {
          blockValue = row.getCell(1).text.trim();
          floorValue = Number(row.getCell(2).value);
          unitNumber = row.getCell(3).text.trim();
          size = Number(row.getCell(4).value);
          price = Number(row.getCell(5).value);
          status = row.getCell(6).text.trim().toLowerCase();

          // Map custom fields from remaining columns
          for (let colNumber = 7; colNumber <= columnHeaders.length; colNumber++) {
            const header = columnHeaders[colNumber - 1];
            if (header && customFieldColumnMap[header]) {
              const cellValue = row.getCell(colNumber).value;
              customFields[customFieldColumnMap[header]] = cellValue;
            }
          }

          if (!blockValue) {
            errors.push(`Row ${rowNumber}: Block is required`);
            return;
          }
          if (isNaN(floorValue) || floorValue <= 0) {
            errors.push(`Row ${rowNumber}: Floor must be a positive number`);
            return;
          }
        } else {
          unitNumber = row.getCell(1).text.trim();
          size = Number(row.getCell(2).value);
          price = Number(row.getCell(3).value);
          status = row.getCell(4).text.trim().toLowerCase();
          blockValue = null;
          floorValue = null;

          // Map custom fields from remaining columns
          for (let colNumber = 5; colNumber <= columnHeaders.length; colNumber++) {
            const header = columnHeaders[colNumber - 1];
            if (header && customFieldColumnMap[header]) {
              const cellValue = row.getCell(colNumber).value;
              customFields[customFieldColumnMap[header]] = cellValue;
            }
          }
        }

        if (!unitNumber) {
          errors.push(`Row ${rowNumber}: Unit number is required`);
          return;
        }

        if (isNaN(size) || size <= 0) {
          errors.push(`Row ${rowNumber}: Size must be a positive number`);
          return;
        }

        if (isNaN(price) || price <= 0) {
          errors.push(`Row ${rowNumber}: Price must be a positive number`);
          return;
        }

        if (status === 'booked') status = 'sold';

        if (!['available', 'sold', 'hold'].includes(status)) {
          errors.push(
            `Row ${rowNumber}: Status must be one of: available, sold, hold`,
          );
          return;
        }

        units.push({
          project: projectId,
          propertyType: propertyTypeId,
          unitNumber,
          size,
          price,
          status,
          block: blockValue,
          floor: floorValue,
          customFields: Object.keys(customFields).length > 0 ? customFields : {},
          createdBy: userId,
        });
      } catch (_error) {
        errors.push(`Row ${rowNumber}: Invalid data format`);
      }

      rowNumber++;
    });
    await job.updateProgress(60);
    await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      {
        progress: 60,
        processedUnits: totalRows,
        successfulUnits: units.length,
        failedUnits: errors.length,
        errorRecords: errors.map((message, index) => ({
          row: index + 2,
          message,
        })),
      },
      { new: true },
    );

    if (units.length === 0) {
      await UnitJob.findOneAndUpdate(
        { jobId: job.id },
        {
          status: 'failed',
          errorRecords:
            errors.length > 0
              ? errors.map((message, index) => ({ row: index + 2, message }))
              : [{ row: 0, message: 'No units found in the file' }],
        },
        { new: true },
      );
      throw new ApiError(
        defaultStatus.OK,
        errors.length > 0
          ? `No valid units found. Errors: ${errors.join(', ')}`
          : 'No units found in the file',
        true,
        '',
        ProjectResponseCodes.PROJECT_ERROR,
      );
    }

    await Unit.insertMany(units);

    await job.updateProgress(100);
    await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      {
        status: 'completed',
        progress: 100,
        successfulUnits: units.length,
        failedUnits: errors.length,
        errorRecords: errors.map((message, index) => ({
          row: index + 2,
          message,
        })),
      },
      { new: true },
    );

    return;
  } catch (error) {
    console.error('Error processing bulk upload:', error);

    await UnitJob.findOneAndUpdate(
      { jobId: job.id },
      {
        status: 'failed',
        errorRecords:
          unitJob.errorRecords.length > 0
            ? unitJob.errorRecords
            : [{ row: 0, message: error.message || 'Unknown error occurred' }],
      },
      { new: true },
    );

    throw error;
  }
}

createWorker(
  'unit',
  async (job) => {
    const { name, data }: any = job;

    try {
      switch (name) {
        case UnitJobType.BULK_UPLOAD: {
          const uploadData = data as BulkUploadData;
          await processBulkUpload(uploadData, job);
          break;
        }
        default:
          return;
      }

      try {
        if (fs.existsSync(data.filePath)) fs.unlinkSync(data.filePath);
      } catch (error) {
        console.error('Error deleting file:', error);
      }
    } catch (error) {
      await UnitJob.findOneAndUpdate(
        { jobId: job.id },
        {
          status: 'failed',
          errorRecords: [
            { row: 0, message: error.message || 'Unknown error occurred' },
          ],
        },
        { new: true },
      );

      throw error;
    }
  },
  {
    concurrency: 2,
  },
);
