import * as assert from 'node:assert/strict';
import { ProcessSharedService } from './process-shared.service';
import { PrismaService } from '../prisma/prisma.service';
import {
  calculateCoatingRow,
  calculateMachineRow,
  calculateMaterialRow,
  calculateProcessRecord,
  parseProcessImportPreview,
  sourceKindFor,
  validateProcessRecord,
} from './process-shared.util';
import { ProcessRecordWriteDto } from './process-shared.types';

async function main() {
  testWheelMaterialCalculation();
  testHolderMaterialCalculations();
  testMachineAndCoatingCalculations();
  testHolderImportPreviewGrouping();
  testWheelImportPreviewGrouping();
  testInvalidRowValidation();
  await testCreateUpdateImportFlow();
  console.log('process-shared tests passed');
}

function testWheelMaterialCalculation() {
  const result = calculateMaterialRow('wheel', {
    materialName: 'RP NYLON BLACK',
    standardPrice: 140,
    pcsWeightGram: 18,
    cavityCount: 8,
  });

  assert.equal(result.materialTotal, 2.52);
}

function testHolderMaterialCalculations() {
  const sheet = calculateMaterialRow('holder', {
    materialName: 'CR SHEET 2 MM - R026',
    materialType: 'SHEET',
    noOfSetsPerSheet: 506,
    oneSheetWeight: 49,
    standardPrice: 75,
    oneSheetRate: 3920,
  });
  assert.equal(sheet.materialTotal, 7.262846);

  const rod = calculateMaterialRow('holder', {
    materialName: 'MS ROD',
    materialType: 'ROD',
    length: 100,
    width: 10,
    standardPrice: 75,
  });
  assert.equal(rod.materialTotal, 4.594579);

  const bearing = calculateMaterialRow('holder', {
    materialName: 'HCHCR BALLS 4.7 MM',
    materialType: 'BEARING',
    quantity: 19,
    standardPrice: 0.13,
  });
  assert.equal(bearing.materialTotal, 2.47);
}

function testMachineAndCoatingCalculations() {
  const firstMachine = calculateMachineRow({
    processName: 'BLANKING',
    machineHourRate: 17,
    labourCostPerHour: 50,
    productionPerHour: 200,
    quantity: 1,
  });
  const secondMachine = calculateMachineRow({
    processName: 'FORMING',
    machineHourRate: 28,
    labourCostPerHour: 50,
    productionPerHour: 120,
    quantity: 1,
  });

  assert.equal(firstMachine.total, 0.335);
  assert.equal(secondMachine.total, 0.65);

  const coating = calculateCoatingRow(
    {
      coatingType: 'EG COATING',
      mode: 'Per KG',
      unitPrice: 20,
    },
    [{ materialName: 'CR SHEET', materialType: 'SHEET', lineCode: 'MAT-1', pcsWeightGram: 96.8379, materialTotal: 0 }],
  );

  assert.equal(coating.total, 1.936758);

  const record = calculateProcessRecord('holder', {
    processCode: 'TEST-HP-EG',
    processName: '2 INCH TEST HOLDER',
    inch: '2 INCH',
    plateCodes: ['P2001', 'P2002'],
    materialRows: [
      {
        materialName: 'CR SHEET 2 MM - R026',
        materialType: 'SHEET',
        noOfSetsPerSheet: 506,
        oneSheetWeight: 49,
        standardPrice: 75,
      },
    ],
    machineRows: [
      {
        processName: 'BLANKING',
        machineHourRate: 17,
        labourCostPerHour: 50,
        productionPerHour: 200,
        productionCostPerPiece: 0.334375,
        quantity: 1,
      },
    ],
    coatingRows: [{ coatingType: 'EG COATING', total: 1.936758 }],
  });

  assert.equal(record.materialTotal, 7.262846);
  assert.equal(record.processTotal, 0.334375);
  assert.equal(record.coatingTotal, 1.936758);
  assert.equal(record.totalAll, 9.533979);
}

function testHolderImportPreviewGrouping() {
  const holderTsv = [
    'Id\tHOLDER NAME // HOLDER PROCESS\tMATERIAL  - code\tLENGTH // NUMBER\tWIDTH // DIA\tCOMPONENTS Thickness\tNo Of Strips\tNo Of Pcs Per Strip\tNo Of Sets Per Sheet\tOne Sheet Weight\tPCS WEIGHT IN GRM\tNO OF PCS IN PER KG\tStd Price\tOne Sheet Rate\tWEIGHT\tPCS IN ONE KG/ONE METER\tPCS IN ONE KG/ONE METER\tTOTAL\tPROCESS TOTAL\tTOTAL ALL\tCombination of plate ',
    '1102.0101.0361 EG\t2 INCH 37.5Y23.5H WOB HOLDER\tCR SHEET 2 MM - R026\t2500\t1250\t2\t\t22\t506\t49\t0.0968379\t10327\t75\t3920\t0\t0\t0\t7.26284585\t1.6113261\t10.81092995\t"P2001,P2002,P2003"',
    '\tPROCESS\tPRESS CAPACITY\tPRESS RUNNING COST/HR\tLABOUR COST/HR\tPRODUTION / HR\tPRODUTION COST/PIECE\tONE STRIP CUTTING CHARGE\tONE PCS CUTTING CHARGE\tQTY\tGALVANIZING CR/PER KG\tGalvanizing charge / per pcs\t TOTAL ',
    '\tBLANKING\tPOWER PRESS  3\t17\t50\t200\t0.334375\t\t\t1\t\t\t0.334375',
    '',
    '\t\tEG COATING\t1.936758',
  ].join('\n');

  const preview = parseProcessImportPreview('holder', 'holder_process_summary.xls', Buffer.from(holderTsv, 'utf8'));
  assert.equal(preview.length, 1);
  assert.equal(preview[0].machineRows, 1);
  assert.equal(preview[0].coatingRows, 1);
  assert.deepEqual(preview[0].plateCodes, ['P2001', 'P2002', 'P2003']);
  assert.equal(preview[0].valid, true);
}

function testWheelImportPreviewGrouping() {
  const wheelTsv = [
    'code \tWHEEL PROCESS\tMATERIAL \tLENGTH // RAW MATERIAL UNIT COST\tWIDTH  // WEIGHT OF TREAD GRAMS\tCOMPONENTS Thickness // NO OF CAVITY IN TOOL\tNo Of Strips \tNo Of Pcs Per Strip\tOne Sheet Weight\tOne Sheet Rate\tTOTAL',
    'WH2001\t50DX18WX22H NYLON RP INSERT FOR PRESSED TREAD SLEEVE BEARING\tRP NYLON BLACK\t140\t18\t8\t\t\t\t\t2.52',
    '\t\tBLACK RUBBER\t170\t22\t16\t\t\t\t\t3.74',
    '',
    '\tPROCESS\tMACHINE TONNAGE\t MACHINE HOUR RATE \t NO OF SHOTS PER HOUR \t NO OF PCS PER HOUR \t LABOR COST PER PIECE \t LABOUR COST/HR \t PRODUTION / HR \t PRODUTION COST/PIECE \t TOTAL ',
    '\tPLASTIC INJECTION MOULDING\tAUTO INJECTION MOLDING MACHINE 150 TON\t300\t40\t320\t0.9375',
    '\tRUBBER MOULDING\t\t5\t80\t\t\t50\t80\t1.75\t1.75',
  ].join('\n');

  const preview = parseProcessImportPreview('wheel', 'wheel_process_summary.xls', Buffer.from(wheelTsv, 'utf8'));
  assert.equal(preview.length, 1);
  assert.equal(preview[0].materialRows, 2);
  assert.equal(preview[0].machineRows, 2);
  assert.equal(preview[0].valid, true);
}

function testInvalidRowValidation() {
  const errors = validateProcessRecord('holder', {
    processCode: '',
    processName: '',
    plateCodes: [],
    materialRows: [],
    machineRows: [],
    coatingRows: [],
  });

  assert.ok(errors.includes('Code is required.'));
  assert.ok(errors.includes('Process name is required.'));
  assert.ok(errors.includes('Combination of plate is required for holder process.'));
  assert.ok(errors.includes('Add at least one material row.'));
}

async function testCreateUpdateImportFlow() {
  const prisma = new PrismaService();
  await prisma.$connect();
  const service = new ProcessSharedService(prisma);
  const processCode = 'TEST-HP-EG';
  const sourceKind = sourceKindFor('holder');

  await prisma.sfgCostingRecord.deleteMany({
    where: { itemCode: processCode, sourceKind },
  });

  const createPayload = holderPayload({
    processCode,
    processName: '2 INCH TEST HOLDER',
    plateCodes: ['P2001', 'P2002'],
    materialRows: [
      {
        materialName: 'CR SHEET 2 MM - R026',
        materialType: 'SHEET',
        noOfSetsPerSheet: 506,
        oneSheetWeight: 49,
        standardPrice: 75,
      },
    ],
    machineRows: [
      {
        processName: 'BLANKING',
        machineHourRate: 17,
        labourCostPerHour: 50,
        productionPerHour: 200,
        productionCostPerPiece: 0.334375,
        quantity: 1,
      },
    ],
    coatingRows: [{ coatingType: 'EG COATING', total: 1.936758 }],
  });

  const created = await service.commitImport('holder', {
    mode: 'validOnly',
    rows: [createPayload],
  });
  assert.equal(created.created, 1);
  assert.equal(created.updated, 0);

  const firstRecord = await prisma.sfgCostingRecord.findFirst({
    where: { itemCode: processCode, sourceKind },
    include: { details: true },
  });
  assert.ok(firstRecord);
  assert.deepEqual(firstRecord?.plateCodes, ['P2001', 'P2002']);
  assert.equal(firstRecord?.details.length, 3);

  const updatedPayload = holderPayload({
    ...createPayload,
    plateCodes: ['P2001', 'P2003'],
    materialRows: [
      {
        materialName: 'CR SHEET 2 MM - R026',
        materialType: 'SHEET',
        noOfSetsPerSheet: 506,
        oneSheetWeight: 49,
        standardPrice: 80,
      },
    ],
  });

  const updated = await service.commitImport('holder', {
    mode: 'validOnly',
    rows: [updatedPayload],
  });
  assert.equal(updated.created, 0);
  assert.equal(updated.updated, 1);

  const secondRecord = await prisma.sfgCostingRecord.findFirst({
    where: { itemCode: processCode, sourceKind },
    include: { details: true },
  });
  assert.ok(secondRecord);
  assert.deepEqual(secondRecord?.plateCodes, ['P2001', 'P2003']);
  assert.equal(secondRecord?.materialCost, 7.747036);
  assert.equal(secondRecord?.details.length, 3);

  await prisma.sfgCostingRecord.deleteMany({
    where: { itemCode: processCode, sourceKind },
  });
  await prisma.$disconnect();
}

function holderPayload(payload: ProcessRecordWriteDto): ProcessRecordWriteDto {
  return {
    processCode: payload.processCode,
    processName: payload.processName,
    processType: 'Holder Process',
    inch: payload.inch || '2 INCH',
    finishVariant: payload.finishVariant || 'EG',
    plateCodes: payload.plateCodes || [],
    materialRows: payload.materialRows || [],
    machineRows: payload.machineRows || [],
    coatingRows: payload.coatingRows || [],
    status: payload.status || 'Active',
    createdBy: payload.createdBy || 'Test Runner',
    sourceFile: payload.sourceFile || 'process-shared.util.test.ts',
  };
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
