import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { Prisma } from '../generated/prisma/index';
import * as crypto from 'crypto';
import * as tls from 'tls';
import * as XLSX from 'xlsx';
import { exportRows } from '../common/tabular-file.util';
import { PrismaService } from '../prisma/prisma.service';

type SetQuery = {
  page?: string;
  pageSize?: string;
  search?: string;
  sortBy?: string;
  sortOrder?: string;
};

type CodeGeneratorQuery = SetQuery & {
  inch?: string;
};

type CodeGeneratorSetQuery = {
  inch?: string;
  search?: string;
  limit?: string;
};

type CreateSetDto = {
  productInch?: number | string;
  holderCode?: string;
  plateCodes?: string[];
  finishes?: CreateFinishDto[];
  finishTypes?: string[];
  wheelCodes?: string[];
};

type CreateFinishDto = {
  type?: string;
  shortLabel?: string;
  label?: string;
  finishCode?: string;
  frameColor?: string;
  frameColorCode?: string;
  status?: string;
};

type FinishSelection = {
  type: string;
  shortLabel: string;
  label: string;
};

type CodeGeneratorImportDto = {
  content?: string;
  fileName?: string;
  inch?: number | string;
};

type CodeGeneratorImportFile = {
  buffer?: Buffer;
  originalname?: string;
  mimetype?: string;
};

type CodeGeneratorEmailSettingsDto = {
  recipientEmail?: string;
  ccEmail?: string;
};

type CodeGeneratorDeleteOtpDto = {
  startCode?: string;
  endCode?: string;
};

type CodeGeneratorDeleteConfirmDto = {
  requestId?: string;
  otp?: string;
};

type CodeGeneratorTemplateRow = {
  setId: string;
  holderCode: string;
  holderName: string;
  plateCode: string;
  plateName: string;
  brakeName: string;
  finishType: string;
  finishLabel: string;
  wheelCode: string;
  wheelName: string;
  productInch: number;
};

type ImportHolderOption = {
  holderCode: string;
  holderName: string;
  holderInchValue: number;
};

type ImportPlateOption = {
  holderCode: string;
  plateCode: string;
  plateName: string;
  brakeName: string;
};

type ImportWheelOption = {
  wheelCode: string;
  wheelName: string;
  wheelType: string;
  bearingType: string;
  wheelInchValue: number;
};

type ResolvedImportTemplate = Omit<CodeGeneratorTemplateRow, 'setId'> & {
  wheelType: string;
  bearingType: string;
  wheelInchValue: number;
};

type ImportResolutionContext = {
  holdersByInch: Map<number, ImportHolderOption[]>;
  platesByHolderCode: Map<string, ImportPlateOption[]>;
  wheelsByNormalizedName: Map<string, ImportWheelOption>;
};

type ParsedCodeGeneratorRow = {
  rowNumber: number;
  holderName: string;
  wheelName: string;
  productCode: string;
  productInch: number;
};

type CodeGeneratorSheetInput = {
  fileName?: string;
  content?: string;
  buffer?: Buffer;
};

type ProductCodePoint = {
  raw: string;
  prefix: string;
  number: number | null;
};

const finishCatalog = [
  {
    type: 'EG_COATED',
    shortLabel: 'EG COATED',
    label: 'ELECTRO GALVANISED ZINC COATING',
  },
  {
    type: 'CED_COATED',
    shortLabel: 'CED COATED',
    label: 'CED COATING',
  },
  {
    type: 'POWDER_COATED',
    shortLabel: 'POWDER COATED',
    label: 'POWDER COATING',
  },
] as const;

const sortColumns = new Set([
  'setNumber',
  'holderName',
  'plateCount',
  'finishCount',
  'wheelCount',
  'updatedAt',
]);

const codeGeneratorSortColumns: Record<string, keyof Prisma.SetProductOrderByWithRelationInput> = {
  code: 'productCode',
  holderName: 'holderName',
  wheelName: 'wheelName',
  productInch: 'productInch',
  updatedAt: 'updatedAt',
};

const codeGeneratorEmailSettingKey = 'code_generator_delete';
const defaultDeleteRecipientEmail = 'cmt@starline.in';
const defaultDeleteCcEmail = 'accounts@starlinecastors.com';
const deleteOtpTtlMs = 10 * 60 * 1000;

@Injectable()
export class SetCreatorService {
  constructor(private readonly prisma: PrismaService) {}

  async list(query: SetQuery) {
    const page = this.toPositiveNumber(query.page, 1);
    const pageSize = Math.min(this.toPositiveNumber(query.pageSize, 10), 100);
    const sortBy = sortColumns.has(query.sortBy || '')
      ? query.sortBy || 'setNumber'
      : 'setNumber';
    const sortOrder = query.sortOrder === 'desc' ? 'desc' : 'asc';
    const search = String(query.search || '').trim();

    const where: Prisma.SetCreatorWhereInput = search
      ? {
          OR: [
            { holderName: { contains: search, mode: 'insensitive' } },
            { holderCode: { contains: search, mode: 'insensitive' } },
          ],
        }
      : {};

    const [total, items] = await Promise.all([
      this.prisma.setCreator.count({ where }),
      this.prisma.setCreator.findMany({
        where,
        orderBy: {
          [sortBy]: sortOrder,
        },
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
    ]);

    return {
      items,
      meta: {
        total,
        page,
        pageSize,
        totalPages: Math.max(1, Math.ceil(total / pageSize)),
      },
    };
  }

  getFinishOptions() {
    return finishCatalog.map((item) => ({
      type: item.type,
      shortLabel: item.shortLabel,
      label: item.label,
    }));
  }

  async getInchOptions() {
    const items = await this.prisma.holderMaster.findMany({
      distinct: ['holderInchValue'],
      where: { holderInchValue: { gt: 0 } },
      orderBy: { holderInchValue: 'asc' },
      select: { holderInchValue: true },
    });

    return items.map((item) => ({
      value: item.holderInchValue,
      label: `${item.holderInchValue} INCH`,
    }));
  }

  async getHolderOptions(inch: string | undefined) {
    const inchValue = Number.parseFloat(inch || '');

    return this.prisma.holderMaster.findMany({
      where: Number.isFinite(inchValue) && inchValue > 0
        ? { holderInchValue: inchValue }
        : undefined,
      orderBy: { holderName: 'asc' },
      select: {
        holderCode: true,
        holderName: true,
        holderInch: true,
        holderInchValue: true,
      },
    });
  }

  async getPlateOptions(holderCode: string | undefined) {
    return this.prisma.plateMaster.findMany({
      where: holderCode ? { holderCode } : undefined,
      orderBy: { plateName: 'asc' },
      select: {
        plateCode: true,
        plateName: true,
        brakeName: true,
      },
    });
  }

  async getWheelOptions(inch: string | undefined) {
    const inchValue = Number.parseFloat(inch || '');

    return this.prisma.wheelMaster.findMany({
      where: Number.isFinite(inchValue) && inchValue > 0
        ? { wheelInchValue: inchValue }
        : undefined,
      orderBy: { wheelName: 'asc' },
      select: {
        wheelCode: true,
        wheelName: true,
        wheelType: true,
        bearingType: true,
        wheelInchValue: true,
      },
    });
  }

  async listCodeGeneratorProducts(query: CodeGeneratorQuery) {
    const page = this.toPositiveNumber(query.page, 1);
    const pageSize = Math.min(this.toPositiveNumber(query.pageSize, 10), 100);
    const sortBy = codeGeneratorSortColumns[query.sortBy || ''] || 'productCode';
    const sortOrder = query.sortOrder === 'desc' ? 'desc' : 'asc';
    const search = String(query.search || '').trim();
    const inchValue = Number.parseFloat(query.inch || '');

    const where: Prisma.SetProductWhereInput = {
      ...(Number.isFinite(inchValue) && inchValue > 0
        ? { productInch: inchValue }
        : {}),
      ...(search
        ? {
            OR: [
              { productCode: { contains: search, mode: 'insensitive' } },
              { holderName: { contains: search, mode: 'insensitive' } },
              { plateName: { contains: search, mode: 'insensitive' } },
              { brakeName: { contains: search, mode: 'insensitive' } },
              { finishLabel: { contains: search, mode: 'insensitive' } },
              { wheelName: { contains: search, mode: 'insensitive' } },
            ],
          }
        : {}),
    };

    const [total, products] = await Promise.all([
      this.prisma.setProduct.count({ where }),
      this.prisma.setProduct.findMany({
        where,
        orderBy: { [sortBy]: sortOrder },
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
    ]);

    return {
      items: products.map((product) => ({
        id: product.id,
        code: product.productCode,
        holderName: buildCodeGeneratorHolderName(
          product.holderName,
          product.plateName,
          product.brakeName,
          formatFinishProductLabel(product.finishType, product.finishLabel),
        ),
        wheelName: product.wheelName,
        productInch: product.productInch,
      })),
      meta: {
        total,
        page,
        pageSize,
        totalPages: Math.max(1, Math.ceil(total / pageSize)),
      },
    };
  }

  async getCodeGeneratorSetOptions(query: CodeGeneratorSetQuery) {
    const inchValue = Number.parseFloat(query.inch || '');
    const search = cleanText(query.search);
    const limit = Math.min(this.toPositiveNumber(query.limit, 50), 100);
    const searchSetNumber = /^\d+$/.test(search)
      ? Number.parseInt(search, 10)
      : undefined;
    const searchFilters: Prisma.SetCreatorWhereInput[] = search
      ? [
          { holderName: { contains: search, mode: 'insensitive' } },
          { holderCode: { contains: search, mode: 'insensitive' } },
          ...(typeof searchSetNumber === 'number'
            ? [{ setNumber: searchSetNumber }]
            : []),
        ]
      : [];

    return this.prisma.setCreator.findMany({
      where: {
        ...(Number.isFinite(inchValue) && inchValue > 0
          ? { productInch: inchValue }
          : {}),
        ...(searchFilters.length
          ? {
              OR: searchFilters,
            }
          : {}),
      },
      orderBy: { setNumber: search ? 'asc' : 'desc' },
      take: limit,
      select: {
        id: true,
        setNumber: true,
        holderName: true,
        productInch: true,
      },
    });
  }

  async getCodeGeneratorDeleteEmailSettings() {
    const settings = await this.prisma.codeGeneratorEmailSetting.findUnique({
      where: { key: codeGeneratorEmailSettingKey },
    });

    return {
      recipientEmail: settings?.recipientEmail || defaultDeleteRecipientEmail,
      ccEmail: settings?.ccEmail || defaultDeleteCcEmail,
    };
  }

  async updateCodeGeneratorDeleteEmailSettings(dto: CodeGeneratorEmailSettingsDto) {
    const recipientEmail = cleanText(dto.recipientEmail).toLowerCase();
    const ccEmail = cleanText(dto.ccEmail).toLowerCase();

    if (!isEmail(recipientEmail)) {
      throw new BadRequestException(['A valid recipient email is required.']);
    }

    if (ccEmail && !areEmails(ccEmail)) {
      throw new BadRequestException(['CC must contain valid email addresses.']);
    }

    const settings = await this.prisma.codeGeneratorEmailSetting.upsert({
      where: { key: codeGeneratorEmailSettingKey },
      create: {
        key: codeGeneratorEmailSettingKey,
        recipientEmail,
        ccEmail,
      },
      update: {
        recipientEmail,
        ccEmail,
      },
    });

    return {
      recipientEmail: settings.recipientEmail,
      ccEmail: settings.ccEmail || '',
    };
  }

  async requestCodeGeneratorDeleteOtp(dto: CodeGeneratorDeleteOtpDto) {
    const startCode = cleanText(dto.startCode).toUpperCase();
    const endCode = cleanText(dto.endCode || dto.startCode).toUpperCase();

    if (!startCode || !endCode) {
      throw new BadRequestException(['Start code and end code are required.']);
    }

    const products = await this.findProductsInCodeRange(startCode, endCode);
    if (!products.length) {
      throw new BadRequestException(['No product codes found in this range.']);
    }

    const settings = await this.getCodeGeneratorDeleteEmailSettings();
    const requestId = crypto.randomUUID();
    const otp = String(crypto.randomInt(100000, 1000000));
    const expiresAt = new Date(Date.now() + deleteOtpTtlMs);

    await this.prisma.codeGeneratorDeleteOtp.create({
      data: {
        id: requestId,
        startCode,
        endCode,
        productIds: products.map((product) => product.id),
        productCount: products.length,
        recipientEmail: settings.recipientEmail,
        ccEmail: settings.ccEmail,
        otpHash: hashDeleteOtp(requestId, otp),
        expiresAt,
      },
    });

    try {
      await sendSmtpMail({
        to: settings.recipientEmail,
        cc: settings.ccEmail,
        subject: 'Product code delete OTP',
        html: buildDeleteOtpEmail({
          otp,
          startCode,
          endCode,
          count: products.length,
          expiresAt,
        }),
      });
    } catch (error) {
      await this.prisma.codeGeneratorDeleteOtp.delete({ where: { id: requestId } });
      throw new BadRequestException(['Unable to send OTP email. Check SMTP settings.']);
    }

    return {
      requestId,
      matchedCount: products.length,
      expiresAt,
      message: `OTP sent to ${settings.recipientEmail}.`,
    };
  }

  async confirmCodeGeneratorDelete(dto: CodeGeneratorDeleteConfirmDto) {
    const requestId = cleanText(dto.requestId);
    const otp = cleanText(dto.otp);

    if (!requestId || !otp) {
      throw new BadRequestException(['OTP is required.']);
    }

    const request = await this.prisma.codeGeneratorDeleteOtp.findUnique({
      where: { id: requestId },
    });

    if (!request || request.usedAt) {
      throw new BadRequestException(['This OTP request is no longer valid.']);
    }

    if (request.expiresAt.getTime() < Date.now()) {
      throw new BadRequestException(['OTP has expired. Generate a new OTP.']);
    }

    if (request.otpHash !== hashDeleteOtp(request.id, otp)) {
      throw new BadRequestException(['Invalid OTP.']);
    }

    const deleted = await this.prisma.$transaction(async (tx) => {
      const result = await tx.setProduct.deleteMany({
        where: { id: { in: request.productIds } },
      });
      await tx.codeGeneratorDeleteOtp.update({
        where: { id: request.id },
        data: { usedAt: new Date() },
      });
      return result.count;
    });

    return {
      deleted,
      message: `${deleted} product codes deleted.`,
    };
  }

  async detail(id: string) {
    const set = await this.prisma.setCreator.findUnique({
      where: { id },
      include: {
        plates: { orderBy: { createdAt: 'asc' } },
        finishes: { orderBy: { createdAt: 'asc' } },
        wheels: { orderBy: { createdAt: 'asc' } },
        products: {
          orderBy: { productCode: 'asc' },
        },
      },
    });

    if (!set) {
      throw new NotFoundException('Set not found');
    }

    return set;
  }

  async create(dto: CreateSetDto) {
    const productInch = Number.parseFloat(String(dto.productInch ?? ''));
    const holderCode = String(dto.holderCode || '').trim().toUpperCase();
    const plateCodes = uniqueTextArray(dto.plateCodes);
    const finishes = this.resolveFinishSelections(dto);
    const finishTypes = finishes.map((finish) => finish.type);
    const wheelCodes = uniqueTextArray(dto.wheelCodes);

    const errors: string[] = [];

    if (!Number.isFinite(productInch) || productInch <= 0) {
      errors.push('Product inch is required.');
    }
    if (!holderCode) {
      errors.push('Holder is required.');
    }
    if (!plateCodes.length) {
      errors.push('Add at least one plate.');
    }
    if (!finishTypes.length) {
      errors.push('Add at least one finish.');
    }
    if (!wheelCodes.length) {
      errors.push('Add at least one wheel.');
    }

    if (errors.length) {
      throw new BadRequestException(errors);
    }

    const [holder, plates, wheels] = await Promise.all([
      this.prisma.holderMaster.findUnique({
        where: { holderCode },
      }),
      this.prisma.plateMaster.findMany({
        where: { plateCode: { in: plateCodes } },
      }),
      this.prisma.wheelMaster.findMany({
        where: { wheelCode: { in: wheelCodes } },
      }),
    ]);

    if (!holder) {
      throw new BadRequestException(['Selected holder was not found.']);
    }

    if (holder.holderInchValue !== productInch) {
      throw new BadRequestException(['Selected holder does not match the chosen inch.']);
    }

    if (plates.length !== plateCodes.length) {
      throw new BadRequestException(['One or more selected plates were not found.']);
    }

    if (wheels.length !== wheelCodes.length) {
      throw new BadRequestException(['One or more selected wheels were not found.']);
    }

    const invalidWheel = wheels.find((wheel) => wheel.wheelInchValue !== productInch);
    if (invalidWheel) {
      throw new BadRequestException([
        `Wheel ${invalidWheel.wheelCode} does not match the selected inch.`,
      ]);
    }

    const invalidPlate = plates.find((plate) => plate.holderCode !== holderCode);
    if (invalidPlate) {
      throw new BadRequestException([
        `Plate ${invalidPlate.plateCode} does not belong to the selected holder.`,
      ]);
    }

    const conflicts = await this.findDuplicateProducts(holderCode, plateCodes, finishTypes, wheelCodes);
    if (conflicts.length) {
      throw new BadRequestException([
        `Some products already exist: ${conflicts.slice(0, 3).join(', ')}`,
      ]);
    }

    const nextSetNumber = await this.getNextSetNumber();

    return this.prisma.$transaction(async (tx) => {
      const set = await tx.setCreator.create({
        data: {
          setNumber: nextSetNumber,
          productInch,
          holderCode: holder.holderCode,
          holderName: holder.holderName,
          plateCount: plates.length,
          finishCount: finishes.length,
          wheelCount: wheels.length,
        },
      });

      if (plates.length) {
        await tx.setCreatorPlate.createMany({
          data: plates.map((plate) => ({
            setId: set.id,
            plateCode: plate.plateCode,
            plateName: plate.plateName,
            brakeName: plate.brakeName,
          })),
        });
      }

      if (finishes.length) {
        await tx.setCreatorFinish.createMany({
          data: finishes.map((finish) => ({
            setId: set.id,
            finishType: finish.type,
            finishLabel: finish.shortLabel,
          })),
        });
      }

      if (wheels.length) {
        await tx.setCreatorWheel.createMany({
          data: wheels.map((wheel) => ({
            setId: set.id,
            wheelCode: wheel.wheelCode,
            wheelName: wheel.wheelName,
            wheelType: wheel.wheelType,
            bearingType: wheel.bearingType,
            wheelInchValue: wheel.wheelInchValue,
          })),
        });
      }

      return tx.setCreator.findUniqueOrThrow({
        where: { id: set.id },
        include: {
          plates: { orderBy: { createdAt: 'asc' } },
          finishes: { orderBy: { createdAt: 'asc' } },
          wheels: { orderBy: { createdAt: 'asc' } },
          products: { orderBy: { productCode: 'asc' } },
        },
      });
    });
  }

  async exportProducts(id: string | string[]) {
    const setIds = Array.isArray(id) ? uniqueTextArray(id) : uniqueTextArray([id]);

    if (!setIds.length) {
      throw new BadRequestException('Choose at least one set to export.');
    }

    const rows = await this.buildCodeGeneratorTemplateRows(setIds);
    const existingProducts = rows.length
      ? await this.prisma.setProduct.findMany({
          where: {
            OR: rows.map((row) => ({
              holderCode: row.holderCode,
              plateCode: row.plateCode,
              finishType: row.finishType,
              wheelCode: row.wheelCode,
            })),
          },
          select: {
            holderCode: true,
            plateCode: true,
            finishType: true,
            wheelCode: true,
            productCode: true,
          },
        })
      : [];
    const existingCodeByCombination = new Map(
      existingProducts.map((product) => [combinationKey(product), product.productCode]),
    );

    return exportRows(
      'set_creator_code_generator',
      rows.map((row, index) => ({
        Id: index + 1,
        HOLDER: buildCodeGeneratorHolderName(
          row.holderName,
          row.plateName,
          row.brakeName,
          formatFinishProductLabel(row.finishType, row.finishLabel),
        ),
        WHEEL: row.wheelName,
        CODE: existingCodeByCombination.get(combinationKey(row)) || '',
        'PRODUCT INCH': row.productInch,
      })),
      'csv',
    );
  }

  async importCodeGeneratorProducts(dto: CodeGeneratorImportDto, file?: CodeGeneratorImportFile) {
    const parsed = parseCodeGeneratorSheet({
      fileName: file?.originalname || dto.fileName,
      content: dto.content,
      buffer: file?.buffer,
    });
    const inchValue = Number.parseFloat(String(dto.inch || ''));
    const errors = [...parsed.errors];
    const rows = parsed.rows.filter((row) =>
      Number.isFinite(inchValue) && inchValue > 0
        ? row.productInch === inchValue
        : true,
    );

    if (parsed.rows.length && !rows.length) {
      errors.push(`No rows matched ${inchValue} inch.`);
    }

    if (!rows.length) {
      return { created: 0, skipped: 0, errors };
    }

    const codeCounts = new Map<string, number>();
    rows.forEach((row) => {
      codeCounts.set(row.productCode, (codeCounts.get(row.productCode) || 0) + 1);
    });

    const duplicateUploadCodes = new Set(
      [...codeCounts.entries()].filter(([, count]) => count > 1).map(([code]) => code),
    );
    const productCodes = [...new Set(rows.map((row) => row.productCode))];
    const existingCodes = await this.prisma.setProduct.findMany({
      where: { productCode: { in: productCodes } },
      select: { productCode: true },
    });
    const existingCodeSet = new Set(existingCodes.map((product) => product.productCode));

    const inchValues = [...new Set(rows.map((row) => row.productInch))];
    const resolutionContext = await this.buildImportResolutionContext(inchValues);
    const templates = await this.buildCodeGeneratorTemplateRows(undefined, inchValues);
    const templateByImportKey = new Map<string, CodeGeneratorTemplateRow>();
    const templateByResolvedKey = new Map<string, CodeGeneratorTemplateRow>();
    templates.forEach((template) => {
      templateByImportKey.set(
        importCombinationKey(
          buildCodeGeneratorHolderName(
            template.holderName,
            template.plateName,
            template.brakeName,
            formatFinishProductLabel(template.finishType, template.finishLabel),
          ),
          template.wheelName,
          template.productInch,
        ),
        template,
      );
      templateByResolvedKey.set(resolvedTemplateKey(template), template);
    });

    const createRows: Prisma.SetProductCreateManyInput[] = [];
    let nextSetNumber = await this.getNextSetNumber();

    for (const row of rows) {
      if (duplicateUploadCodes.has(row.productCode)) {
        errors.push(`Row ${row.rowNumber}: product code ${row.productCode} is repeated in the file.`);
        continue;
      }

      if (existingCodeSet.has(row.productCode)) {
        errors.push(`Row ${row.rowNumber}: product code ${row.productCode} already exists.`);
        continue;
      }

      const importKey = importCombinationKey(row.holderName, row.wheelName, row.productInch);
      let template = templateByImportKey.get(
        importKey,
      );

      if (!template) {
        const resolved = this.resolveImportTemplateRow(row, resolutionContext);
        if (!resolved) {
          errors.push(`Row ${row.rowNumber}: combination could not be resolved from master data.`);
          continue;
        }

        const resolvedKey = resolvedTemplateKey(resolved);
        template = templateByResolvedKey.get(resolvedKey);

        if (!template) {
          template = await this.createImportTemplateSet(resolved, nextSetNumber);
          templateByResolvedKey.set(resolvedKey, template);
          nextSetNumber += 1;
        }

        templateByImportKey.set(importKey, template);
      }

      const finishLabel = formatFinishProductLabel(template.finishType, template.finishLabel);
      createRows.push({
        setId: template.setId,
        holderCode: template.holderCode,
        holderName: template.holderName,
        plateCode: template.plateCode,
        plateName: template.plateName,
        brakeName: template.brakeName,
        finishType: template.finishType,
        finishLabel,
        wheelCode: template.wheelCode,
        wheelName: template.wheelName,
        productName: buildProductName(
          template.holderName,
          template.plateName,
          template.brakeName,
          finishLabel,
          template.wheelName,
        ),
        productCode: row.productCode,
        productInch: template.productInch,
      });
    }

    let created = 0;
    if (createRows.length) {
      const result = await this.prisma.setProduct.createMany({
        data: createRows,
        skipDuplicates: true,
      });
      created = result.count;
    }

    return {
      created,
      skipped: rows.length - created,
      errors,
    };
  }

  private async buildCodeGeneratorTemplateRows(setIds?: string[], inchValues?: number[]) {
    const sets = await this.prisma.setCreator.findMany({
      where: {
        ...(setIds?.length
          ? {
              id: setIds.length === 1
                ? setIds[0]
                : { in: setIds },
            }
          : {}),
        ...(inchValues?.length ? { productInch: { in: inchValues } } : {}),
      },
      include: {
        plates: { orderBy: { createdAt: 'asc' } },
        finishes: { orderBy: { createdAt: 'asc' } },
        wheels: { orderBy: { createdAt: 'asc' } },
      },
      orderBy: { setNumber: 'asc' },
    });

    if (setIds?.length && !sets.length) {
      throw new NotFoundException('Set not found');
    }

    if (setIds?.length) {
      const foundSetIds = new Set(sets.map((set) => set.id));
      const missingSetIds = setIds.filter((setId) => !foundSetIds.has(setId));

      if (missingSetIds.length) {
        throw new NotFoundException('One or more selected sets were not found.');
      }
    }

    const rows: CodeGeneratorTemplateRow[] = [];
    for (const set of sets) {
      for (const plate of set.plates) {
        for (const finish of set.finishes) {
          for (const wheel of set.wheels) {
            rows.push({
              setId: set.id,
              holderCode: set.holderCode,
              holderName: set.holderName,
              plateCode: plate.plateCode,
              plateName: plate.plateName,
              brakeName: plate.brakeName,
              finishType: finish.finishType,
              finishLabel: finish.finishLabel,
              wheelCode: wheel.wheelCode,
              wheelName: wheel.wheelName,
              productInch: set.productInch,
            });
          }
        }
      }
    }

    return rows;
  }

  private async buildImportResolutionContext(inchValues: number[]): Promise<ImportResolutionContext> {
    const holders = await this.prisma.holderMaster.findMany({
      where: inchValues.length ? { holderInchValue: { in: inchValues } } : undefined,
      orderBy: { holderName: 'asc' },
      select: {
        holderCode: true,
        holderName: true,
        holderInchValue: true,
      },
    });
    const holderCodes = holders.map((holder) => holder.holderCode);
    const [plates, wheels] = await Promise.all([
      holderCodes.length
        ? this.prisma.plateMaster.findMany({
            where: { holderCode: { in: holderCodes } },
            orderBy: [{ holderCode: 'asc' }, { plateName: 'asc' }],
            select: {
              holderCode: true,
              plateCode: true,
              plateName: true,
              brakeName: true,
            },
          })
        : Promise.resolve([] as ImportPlateOption[]),
      this.prisma.wheelMaster.findMany({
        where: { status: { equals: 'Active', mode: 'insensitive' } },
        orderBy: { wheelName: 'asc' },
        select: {
          wheelCode: true,
          wheelName: true,
          wheelType: true,
          bearingType: true,
          wheelInchValue: true,
        },
      }),
    ]);

    const holdersByInch = new Map<number, ImportHolderOption[]>();
    holders.forEach((holder) => {
      const items = holdersByInch.get(holder.holderInchValue) || [];
      items.push(holder);
      holdersByInch.set(holder.holderInchValue, items);
    });
    holdersByInch.forEach((items, inch) => {
      holdersByInch.set(
        inch,
        items.sort(
          (left, right) => normalizeImportText(right.holderName).length
            - normalizeImportText(left.holderName).length,
        ),
      );
    });

    const platesByHolderCode = new Map<string, ImportPlateOption[]>();
    plates.forEach((plate) => {
      const items = platesByHolderCode.get(plate.holderCode) || [];
      items.push(plate);
      platesByHolderCode.set(plate.holderCode, items);
    });
    platesByHolderCode.forEach((items, holderCode) => {
      platesByHolderCode.set(
        holderCode,
        items.sort(
          (left, right) => normalizeImportText(formatPlateName(right.plateName, right.brakeName)).length
            - normalizeImportText(formatPlateName(left.plateName, left.brakeName)).length,
        ),
      );
    });

    const wheelsByNormalizedName = new Map<string, ImportWheelOption>();
    wheels.forEach((wheel) => {
      const key = normalizeImportText(wheel.wheelName);
      if (key && !wheelsByNormalizedName.has(key)) {
        wheelsByNormalizedName.set(key, wheel);
      }
    });

    return {
      holdersByInch,
      platesByHolderCode,
      wheelsByNormalizedName,
    };
  }

  private resolveImportTemplateRow(
    row: ParsedCodeGeneratorRow,
    context: ImportResolutionContext,
  ): ResolvedImportTemplate | undefined {
    const normalizedHolder = normalizeImportText(row.holderName);
    const normalizedWheel = normalizeImportText(row.wheelName);
    const wheel = context.wheelsByNormalizedName.get(normalizedWheel);

    if (!wheel) {
      return undefined;
    }

    const holders = context.holdersByInch.get(row.productInch) || [];
    for (const holder of holders) {
      const holderRemainder = stripImportPrefix(normalizedHolder, normalizeImportText(holder.holderName));
      if (!holderRemainder) {
        continue;
      }

      const plates = context.platesByHolderCode.get(holder.holderCode) || [];
      for (const plate of plates) {
        const finishLabel = stripImportPrefix(
          holderRemainder,
          normalizeImportText(formatPlateName(plate.plateName, plate.brakeName)),
        );

        if (!finishLabel) {
          continue;
        }

        return {
          holderCode: holder.holderCode,
          holderName: holder.holderName,
          plateCode: plate.plateCode,
          plateName: plate.plateName,
          brakeName: plate.brakeName,
          finishType: normalizeFinishType(finishLabel),
          finishLabel,
          wheelCode: wheel.wheelCode,
          wheelName: wheel.wheelName,
          wheelType: wheel.wheelType,
          bearingType: wheel.bearingType,
          wheelInchValue: wheel.wheelInchValue,
          productInch: row.productInch,
        };
      }
    }

    return undefined;
  }

  private async createImportTemplateSet(
    template: ResolvedImportTemplate,
    setNumber: number,
  ): Promise<CodeGeneratorTemplateRow> {
    const created = await this.prisma.$transaction(async (tx) => {
      const set = await tx.setCreator.create({
        data: {
          setNumber,
          productInch: template.productInch,
          holderCode: template.holderCode,
          holderName: template.holderName,
          plateCount: 1,
          finishCount: 1,
          wheelCount: 1,
        },
      });

      await tx.setCreatorPlate.create({
        data: {
          setId: set.id,
          plateCode: template.plateCode,
          plateName: template.plateName,
          brakeName: template.brakeName,
        },
      });

      await tx.setCreatorFinish.create({
        data: {
          setId: set.id,
          finishType: template.finishType,
          finishLabel: template.finishLabel,
        },
      });

      await tx.setCreatorWheel.create({
        data: {
          setId: set.id,
          wheelCode: template.wheelCode,
          wheelName: template.wheelName,
          wheelType: template.wheelType,
          bearingType: template.bearingType,
          wheelInchValue: template.wheelInchValue,
        },
      });

      return set;
    });

    return {
      setId: created.id,
      holderCode: template.holderCode,
      holderName: template.holderName,
      plateCode: template.plateCode,
      plateName: template.plateName,
      brakeName: template.brakeName,
      finishType: template.finishType,
      finishLabel: template.finishLabel,
      wheelCode: template.wheelCode,
      wheelName: template.wheelName,
      productInch: template.productInch,
    };
  }

  private async findProductsInCodeRange(startCode: string, endCode: string) {
    const start = parseProductCodePoint(startCode);
    const end = parseProductCodePoint(endCode);

    if (start.number === null || end.number === null) {
      if (start.raw === end.raw) {
        return this.prisma.setProduct.findMany({
          where: { productCode: { equals: start.raw, mode: 'insensitive' } },
          orderBy: { productCode: 'asc' },
          select: { id: true, productCode: true },
        });
      }
      throw new BadRequestException([
        'Code range must use product codes ending with numbers.',
      ]);
    }

    if (start.prefix !== end.prefix) {
      throw new BadRequestException(['Start code and end code must use the same prefix.']);
    }

    const low = Math.min(start.number, end.number);
    const high = Math.max(start.number, end.number);
    const candidates = await this.prisma.setProduct.findMany({
      where: start.prefix
        ? { productCode: { startsWith: start.prefix, mode: 'insensitive' } }
        : undefined,
      orderBy: { productCode: 'asc' },
      select: { id: true, productCode: true },
    });

    return candidates.filter((product) => {
      const point = parseProductCodePoint(product.productCode);
      return point.prefix === start.prefix
        && point.number !== null
        && point.number >= low
        && point.number <= high;
    });
  }

  private resolveFinishSelections(dto: CreateSetDto): FinishSelection[] {
    if (Array.isArray(dto.finishes) && dto.finishes.length) {
      const selected = dto.finishes
        .map((finish) => {
          const rawType = cleanText(
            finish.type || finish.finishCode || finish.shortLabel || finish.label,
          );
          const shortLabel = cleanText(
            finish.shortLabel || finish.label || finish.type || finish.finishCode,
          );
          const label = cleanText(finish.label || shortLabel);

          if (!rawType || !shortLabel) {
            return undefined;
          }

          return {
            type: normalizeFinishType(rawType),
            shortLabel,
            label,
          };
        })
        .filter(Boolean) as FinishSelection[];

      return uniqueFinishes(selected);
    }

    const selected = uniqueTextArray(dto.finishTypes)
      .map((finishType) => finishCatalog.find((item) => item.type === finishType))
      .filter((finish): finish is typeof finishCatalog[number] => Boolean(finish))
      .map((finish) => ({
        type: finish.type,
        shortLabel: finish.shortLabel,
        label: finish.label,
      }));

    return uniqueFinishes(selected);
  }

  private async findDuplicateProducts(
    holderCode: string,
    plateCodes: string[],
    finishTypes: string[],
    wheelCodes: string[],
  ) {
    const existing = await this.prisma.setProduct.findMany({
      where: {
        holderCode,
        plateCode: { in: plateCodes },
        finishType: { in: finishTypes },
        wheelCode: { in: wheelCodes },
      },
      select: {
        plateCode: true,
        finishType: true,
        wheelCode: true,
      },
    });

    return existing.map((item) => `${item.plateCode}/${item.finishType}/${item.wheelCode}`);
  }

  private async getNextSetNumber() {
    const last = await this.prisma.setCreator.findFirst({
      orderBy: { setNumber: 'desc' },
      select: { setNumber: true },
    });

    return (last?.setNumber || 0) + 1;
  }

  private async getNextProductCodeCounter() {
    const last = await this.prisma.setProduct.findFirst({
      orderBy: { productCode: 'desc' },
      select: { productCode: true },
    });

    const numeric = Number.parseInt(last?.productCode?.replace(/\D/g, '') || '0', 10);
    return Number.isFinite(numeric) ? numeric + 1 : 1;
  }

  private toPositiveNumber(value: string | undefined, fallback: number) {
    const parsed = Number.parseInt(value || '', 10);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
  }
}

function buildProductName(
  holderName: string,
  plateName: string,
  brakeName: string,
  finishLabel: string,
  wheelName: string,
) {
  return [holderName, plateName, brakeName, finishLabel, wheelName]
    .map((item) => item.trim())
    .join(' ');
}

function uniqueTextArray(values?: string[]) {
  return [...new Set((values || []).map((item) => String(item || '').trim()).filter(Boolean))];
}

function formatProductCode(counter: number) {
  return `PRD${String(counter).padStart(6, '0')}`;
}

function formatPlateName(plateName: string, brakeName: string) {
  return [plateName, brakeName]
    .map((item) => item.trim())
    .filter(Boolean)
    .join(' ');
}

function formatFinishProductLabel(finishType: string, finishLabel: string) {
  return finishCatalog.find((finish) => finish.type === finishType)?.label
    || finishLabel
    || finishType;
}

function buildCodeGeneratorHolderName(
  holderName: string,
  plateName: string,
  brakeName: string,
  finishLabel: string,
) {
  return [holderName, formatPlateName(plateName, brakeName), finishLabel]
    .map((item) => item.trim())
    .filter(Boolean)
    .join(' ');
}

function combinationKey(value: {
  holderCode: string;
  plateCode: string;
  finishType: string;
  wheelCode: string;
}) {
  return [
    value.holderCode,
    value.plateCode,
    value.finishType,
    value.wheelCode,
  ].map((item) => cleanText(item).toUpperCase()).join('|');
}

function importCombinationKey(holderName: string, wheelName: string, productInch: number) {
  return [
    normalizeImportText(holderName),
    normalizeImportText(wheelName),
    String(productInch),
  ].join('|');
}

function resolvedTemplateKey(value: {
  holderCode: string;
  plateCode: string;
  finishType: string;
  wheelCode: string;
  productInch: number;
}) {
  return [
    cleanText(value.holderCode).toUpperCase(),
    cleanText(value.plateCode).toUpperCase(),
    cleanText(value.finishType).toUpperCase(),
    cleanText(value.wheelCode).toUpperCase(),
    String(value.productInch),
  ].join('|');
}

function normalizeImportText(value: string) {
  return cleanText(value)
    .replace(/^"+|"+$/g, '')
    .replace(/\s+/g, ' ')
    .toUpperCase();
}

function stripImportPrefix(value: string, prefix: string) {
  if (value === prefix) {
    return '';
  }

  if (!value.startsWith(`${prefix} `)) {
    return undefined;
  }

  return value.slice(prefix.length + 1).trim();
}

function parseCodeGeneratorSheet(input: CodeGeneratorSheetInput): {
  rows: ParsedCodeGeneratorRow[];
  errors: string[];
} {
  const matrix = readCodeGeneratorMatrix(input);

  if (matrix.error) {
    return { rows: [], errors: [matrix.error] };
  }

  const rows = matrix.rows.filter((row) =>
    row.some((cell) => cleanText(cell)),
  );
  const errors: string[] = [];

  if (rows.length < 2) {
    return { rows: [], errors: ['File has no product rows.'] };
  }

  const headers = rows[0].map(normalizeHeader);
  const holderIndex = findColumn(headers, ['holder', 'holder name']);
  const wheelIndex = findColumn(headers, ['wheel', 'wheel name']);
  const codeIndex = findColumn(headers, ['code', 'product code']);
  const inchIndex = findColumn(headers, ['product inch', 'inch']);

  if (holderIndex < 0 || wheelIndex < 0 || codeIndex < 0 || inchIndex < 0) {
    return {
      rows: [],
      errors: ['File must include HOLDER, WHEEL, CODE, and PRODUCT INCH columns.'],
    };
  }

  const parsedRows: ParsedCodeGeneratorRow[] = [];
  rows.slice(1).forEach((row, index) => {
    const rowNumber = index + 2;
    const holderName = cleanText(row[holderIndex]);
    const wheelName = cleanText(row[wheelIndex]);
    const productCode = cleanText(row[codeIndex]);
    const productInch = Number.parseFloat(cleanText(row[inchIndex]));

    if (!holderName && !wheelName && !productCode) {
      return;
    }

    if (!holderName || !wheelName) {
      errors.push(`Row ${rowNumber}: HOLDER and WHEEL are required.`);
      return;
    }

    if (!productCode) {
      errors.push(`Row ${rowNumber}: product code is required.`);
      return;
    }

    if (!Number.isFinite(productInch) || productInch <= 0) {
      errors.push(`Row ${rowNumber}: product inch is invalid.`);
      return;
    }

    parsedRows.push({
      rowNumber,
      holderName,
      wheelName,
      productCode,
      productInch,
    });
  });

  return { rows: parsedRows, errors };
}

function readCodeGeneratorMatrix(input: CodeGeneratorSheetInput): {
  rows: string[][];
  error?: string;
} {
  const fileName = cleanText(input.fileName);
  const lowerName = fileName.toLowerCase();

  if (input.buffer?.length) {
    if (lowerName.endsWith('.xls') || lowerName.endsWith('.xlsx')) {
      try {
        const workbook = XLSX.read(input.buffer, { type: 'buffer', cellDates: false });
        const firstSheet = workbook.Sheets[workbook.SheetNames[0] || ''];

        if (!firstSheet) {
          const fallbackText = input.buffer.toString('utf8').replace(/^\uFEFF/, '');
          if (fallbackText.trim() && (fallbackText.includes('\t') || fallbackText.includes(','))) {
            return { rows: parseDelimitedRows(fallbackText) };
          }

          return { rows: [], error: 'Imported Excel file has no worksheet data.' };
        }

        return {
          rows: (XLSX.utils.sheet_to_json(firstSheet, { header: 1, defval: '' }) as unknown[][])
            .map((row) => row.map((cell) => cleanText(cell))),
        };
      } catch {
        const fallbackText = input.buffer.toString('utf8').replace(/^\uFEFF/, '');
        if (fallbackText.trim() && (fallbackText.includes('\t') || fallbackText.includes(','))) {
          return { rows: parseDelimitedRows(fallbackText) };
        }

        return {
          rows: [],
          error: 'Unable to read import file. Upload CSV, TSV, XLS, or XLSX.',
        };
      }
    }

    const text = input.buffer.toString('utf8').replace(/^\uFEFF/, '');
    if (!text.trim()) {
      return { rows: [], error: 'Import file is empty.' };
    }

    return { rows: parseDelimitedRows(text) };
  }

  const text = String(input.content || '').replace(/^\uFEFF/, '');
  if (!text.trim()) {
    return { rows: [], error: 'Import file is empty.' };
  }

  return { rows: parseDelimitedRows(text) };
}

function parseDelimitedRows(text: string): string[][] {
  const firstLine = text.split(/\r?\n/, 1)[0] || '';
  const delimiter = firstLine.includes('\t') ? '\t' : ',';
  const rows: string[][] = [];
  let row: string[] = [];
  let cell = '';
  let inQuotes = false;

  for (let index = 0; index < text.length; index += 1) {
    const char = text[index];
    const nextChar = text[index + 1];

    if (char === '"') {
      if (inQuotes && nextChar === '"') {
        cell += '"';
        index += 1;
      } else {
        inQuotes = !inQuotes;
      }
      continue;
    }

    if (!inQuotes && char === delimiter) {
      row.push(cell);
      cell = '';
      continue;
    }

    if (!inQuotes && (char === '\n' || char === '\r')) {
      if (char === '\r' && nextChar === '\n') {
        index += 1;
      }
      row.push(cell);
      rows.push(row);
      row = [];
      cell = '';
      continue;
    }

    cell += char;
  }

  row.push(cell);
  rows.push(row);
  return rows;
}

function normalizeHeader(value: string) {
  return cleanText(value).toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
}

function findColumn(headers: string[], candidates: string[]) {
  return headers.findIndex((header) => candidates.includes(header));
}

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

function normalizeFinishType(value: string) {
  return cleanText(value)
    .toUpperCase()
    .replace(/[^A-Z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '');
}

function uniqueFinishes(finishes: FinishSelection[]) {
  const map = new Map<string, FinishSelection>();

  for (const finish of finishes) {
    if (!finish.type || map.has(finish.type)) {
      continue;
    }

    map.set(finish.type, finish);
  }

  return [...map.values()];
}

function parseProductCodePoint(value: string): ProductCodePoint {
  const raw = cleanText(value).toUpperCase();
  const match = raw.match(/^(.*?)(\d+)$/);

  if (!match) {
    return { raw, prefix: raw, number: null };
  }

  return {
    raw,
    prefix: match[1],
    number: Number.parseInt(match[2], 10),
  };
}

function isEmail(value: string) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

function splitEmails(value?: string) {
  return cleanText(value)
    .split(',')
    .map((email) => email.trim())
    .filter(Boolean);
}

function areEmails(value: string) {
  return splitEmails(value).every(isEmail);
}

function hashDeleteOtp(requestId: string, otp: string) {
  return crypto
    .createHash('sha256')
    .update(`${requestId}:${otp}:${process.env.DELETE_OTP_SECRET || process.env.JWT_SECRET || 'erp-starline-delete-otp'}`)
    .digest('hex');
}

function buildDeleteOtpEmail(input: {
  otp: string;
  startCode: string;
  endCode: string;
  count: number;
  expiresAt: Date;
}) {
  const expiresAt = input.expiresAt.toLocaleString('en-IN', {
    timeZone: 'Asia/Kolkata',
    dateStyle: 'medium',
    timeStyle: 'short',
  });

  return `
    <div style="font-family:Arial,sans-serif;color:#111827;line-height:1.55">
      <h2 style="margin:0 0 12px">Product Code Delete OTP</h2>
      <p>Use this OTP to delete product codes from <strong>${escapeHtml(input.startCode)}</strong> to <strong>${escapeHtml(input.endCode)}</strong>.</p>
      <p>Matched products: <strong>${input.count}</strong></p>
      <p style="font-size:26px;font-weight:700;letter-spacing:4px;margin:18px 0">${input.otp}</p>
      <p>This OTP expires at ${escapeHtml(expiresAt)}.</p>
    </div>
  `;
}

function escapeHtml(value: string | number) {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

async function sendSmtpMail(input: {
  to: string;
  cc?: string;
  subject: string;
  html: string;
}) {
  const host = process.env.SMTP_HOST || 'mail.castorwheel.co';
  const port = Number.parseInt(process.env.SMTP_PORT || '465', 10);
  const username = process.env.SMTP_USERNAME || 'castorwheel@castorwheel.co';
  const password = process.env.SMTP_PASSWORD || '';
  const fromEmail = process.env.SMTP_FROM_EMAIL || username;
  const fromName = process.env.SMTP_FROM_NAME || 'Castorwheel Support';

  if (!password) {
    throw new Error('SMTP_PASSWORD is not configured.');
  }

  const recipients = [input.to, ...splitEmails(input.cc)];
  const message = buildSmtpMessage({
    fromEmail,
    fromName,
    to: input.to,
    cc: input.cc,
    subject: input.subject,
    html: input.html,
  });

  await withSmtpConnection({ host, port }, async (socket, readResponse) => {
    await expectSmtp(readResponse, [220]);
    await sendSmtpCommand(socket, readResponse, `EHLO ${process.env.SMTP_EHLO_NAME || 'erp-starline.local'}`, [250]);
    await sendSmtpCommand(socket, readResponse, 'AUTH LOGIN', [334]);
    await sendSmtpCommand(socket, readResponse, Buffer.from(username).toString('base64'), [334]);
    await sendSmtpCommand(socket, readResponse, Buffer.from(password).toString('base64'), [235]);
    await sendSmtpCommand(socket, readResponse, `MAIL FROM:<${fromEmail}>`, [250]);

    for (const recipient of recipients) {
      await sendSmtpCommand(socket, readResponse, `RCPT TO:<${recipient}>`, [250, 251]);
    }

    await sendSmtpCommand(socket, readResponse, 'DATA', [354]);
    socket.write(`${message}\r\n.\r\n`);
    await expectSmtp(readResponse, [250]);
    await sendSmtpCommand(socket, readResponse, 'QUIT', [221]);
  });
}

function buildSmtpMessage(input: {
  fromEmail: string;
  fromName: string;
  to: string;
  cc?: string;
  subject: string;
  html: string;
}) {
  const headers = [
    `From: ${formatEmailHeader(input.fromName, input.fromEmail)}`,
    `To: ${input.to}`,
    ...(cleanText(input.cc) ? [`Cc: ${input.cc}`] : []),
    `Subject: ${input.subject}`,
    'MIME-Version: 1.0',
    'Content-Type: text/html; charset=UTF-8',
    `Date: ${new Date().toUTCString()}`,
    `Message-ID: <${crypto.randomUUID()}@erp-starline.local>`,
  ];

  return `${headers.join('\r\n')}\r\n\r\n${dotStuff(input.html)}`;
}

function formatEmailHeader(name: string, email: string) {
  const safeName = cleanText(name).replace(/"/g, '\\"');
  return safeName ? `"${safeName}" <${email}>` : `<${email}>`;
}

function dotStuff(value: string) {
  return value.replace(/\r?\n\./g, '\r\n..');
}

async function withSmtpConnection(
  options: { host: string; port: number },
  callback: (
    socket: tls.TLSSocket,
    readResponse: () => Promise<{ code: number; message: string }>,
  ) => Promise<void>,
) {
  const socket = tls.connect({
    host: options.host,
    port: options.port,
    servername: options.host,
  });
  const readResponse = createSmtpReader(socket);

  try {
    await callback(socket, readResponse);
  } finally {
    socket.destroy();
  }
}

function createSmtpReader(socket: tls.TLSSocket) {
  let buffer = '';

  return () => new Promise<{ code: number; message: string }>((resolve, reject) => {
    const timeout = setTimeout(() => {
      cleanup();
      reject(new Error('SMTP response timed out.'));
    }, 15000);

    const cleanup = () => {
      clearTimeout(timeout);
      socket.off('data', onData);
      socket.off('error', onError);
    };

    const onError = (error: Error) => {
      cleanup();
      reject(error);
    };

    const onData = (chunk: Buffer) => {
      buffer += chunk.toString('utf8');
      const parsed = readCompleteSmtpResponse(buffer);
      if (!parsed) {
        return;
      }

      buffer = buffer.slice(parsed.length);
      cleanup();
      resolve(parsed.response);
    };

    socket.on('data', onData);
    socket.on('error', onError);

    const parsed = readCompleteSmtpResponse(buffer);
    if (parsed) {
      buffer = buffer.slice(parsed.length);
      cleanup();
      resolve(parsed.response);
    }
  });
}

function readCompleteSmtpResponse(buffer: string) {
  const lines = buffer.split(/\r?\n/);
  let consumedLength = 0;

  for (const line of lines) {
    if (!line) {
      consumedLength += 2;
      continue;
    }

    consumedLength += line.length + 2;
    const match = line.match(/^(\d{3})\s/);
    if (match) {
      const message = buffer.slice(0, consumedLength).trim();
      return {
        length: consumedLength,
        response: {
          code: Number.parseInt(match[1], 10),
          message,
        },
      };
    }
  }

  return undefined;
}

async function sendSmtpCommand(
  socket: tls.TLSSocket,
  readResponse: () => Promise<{ code: number; message: string }>,
  command: string,
  expectedCodes: number[],
) {
  socket.write(`${command}\r\n`);
  await expectSmtp(readResponse, expectedCodes);
}

async function expectSmtp(
  readResponse: () => Promise<{ code: number; message: string }>,
  expectedCodes: number[],
) {
  const response = await readResponse();
  if (!expectedCodes.includes(response.code)) {
    throw new Error(`SMTP error: ${response.message}`);
  }
}
