import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Machinery, Prisma } from '../generated/prisma/index';
import { exportRows, normalizeImportRow, parseImportRows } from '../common/tabular-file.util';
import { PrismaService } from '../prisma/prisma.service';
import { ImportCommitDto, ImportPreviewDto, MachineryQuery, MachineryWriteDto } from './machinery.dto';

const moduleTypes = ['General', 'Injection Moulding', 'Press'];
const sortColumns = new Set(['machineryName', 'machineryNumber', 'eachHp', 'hourRate', 'moduleType', 'status', 'updatedAt']);

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

  async list(query: MachineryQuery) {
    const page = positiveInt(query.page, 1);
    const pageSize = Math.min(positiveInt(query.pageSize, 10), 100);
    const where = this.buildWhere(query);
    const sortBy = sortColumns.has(query.sortBy || '') ? (query.sortBy as keyof Machinery) : 'machineryName';
    const sortOrder = query.sortOrder === 'desc' ? 'desc' : 'asc';
    const [total, items] = await Promise.all([
      this.prisma.machinery.count({ where }),
      this.prisma.machinery.findMany({
        where,
        orderBy: { [sortBy]: sortOrder } as Prisma.MachineryOrderByWithRelationInput,
        skip: (page - 1) * pageSize,
        take: pageSize,
      }),
    ]);
    return { items, meta: { total, page, pageSize, totalPages: Math.max(1, Math.ceil(total / pageSize)) }, filterOptions: this.options() };
  }

  async detail(id: string) {
    const item = await this.prisma.machinery.findUnique({ where: { id } });
    if (!item) throw new NotFoundException('Machinery not found');
    return item;
  }

  async create(dto: MachineryWriteDto) {
    const data = this.validate(dto) as Prisma.MachineryCreateInput;
    return this.prisma.machinery.create({ data });
  }

  async update(id: string, dto: MachineryWriteDto) {
    await this.detail(id);
    const data = this.validate(dto) as Prisma.MachineryUpdateInput;
    return this.prisma.machinery.update({ where: { id }, data });
  }

  async delete(id: string) {
    await this.detail(id);
    await this.prisma.machinery.delete({ where: { id } });
    return { deleted: true };
  }

  options() {
    return {
      moduleTypes,
      statuses: ['Active', 'Inactive'],
      sampleHeaders: [
        'Module Type',
        'Machinery Name',
        'Machinery Number',
        'Each HP',
        'Hour Rate',
        'Name',
        'Code',
        'Tonnage',
        'No Of Platens',
        'Platen Size',
        'Power Pack HP',
        'Heater Capacity',
        'Heater Per Platen',
        'No Of Plates',
        'Cooling Tower',
        'Power Pack',
        'Heater Pack',
        'Cooling Tower Cost',
        'Status',
      ],
    };
  }

  async export(moduleName: string, query: MachineryQuery & { format?: string }) {
    const moduleType = moduleFromSlug(moduleName);
    const response = await this.list({ ...query, moduleType, page: '1', pageSize: '10000' });
    return exportRows(
      `machinery_${moduleName}`,
      response.items.map((item) => ({
        'Module Type': item.moduleType,
        'Machinery Name': item.machineryName,
        'Machinery Number': item.machineryNumber,
        'Each HP': item.eachHp,
        'Hour Rate': item.hourRate,
        Name: item.name || '',
        Code: item.code || '',
        Tonnage: item.tonnage || '',
        'No Of Platens': item.noOfPlatens || '',
        'Platen Size': item.platenSize || '',
        'Power Pack HP': item.powerPackHp || '',
        'Heater Capacity': item.heaterCapacity || '',
        'Heater Per Platen': item.heaterPerPlaten || '',
        'No Of Plates': item.noOfPlates || '',
        'Cooling Tower': item.coolingTower || '',
        'Power Pack': item.powerPack || '',
        'Heater Pack': item.heaterPack || '',
        'Cooling Tower Cost': item.coolingTowerCost || '',
        Status: item.status,
      })),
      query.format || 'xlsx',
    );
  }

  sample(moduleName: string, format = 'xlsx') {
    const moduleType = moduleFromSlug(moduleName);
    const row: Record<string, unknown> = {
      'Module Type': moduleType,
      'Machinery Name': moduleType === 'Press' ? 'Hydraulic Press Line 1' : moduleType === 'Injection Moulding' ? 'Injection Moulding 120T' : 'General Machine',
      'Machinery Number': moduleType === 'Press' ? 'PRS-001' : moduleType === 'Injection Moulding' ? 'IMM-001' : 'MCH-001',
      'Each HP': 10,
      'Hour Rate': 450,
      Name: moduleType === 'Injection Moulding' ? 'Injection Machine 120T' : '',
      Code: moduleType === 'Injection Moulding' ? 'IMM120' : '',
      Tonnage: moduleType === 'Injection Moulding' ? 120 : '',
      'No Of Platens': moduleType === 'Press' ? 2 : '',
      'Platen Size': moduleType === 'Press' ? '600 X 600' : '',
      'Power Pack HP': moduleType === 'Press' ? 7.5 : '',
      'Heater Capacity': moduleType === 'Press' ? 5 : '',
      'Heater Per Platen': moduleType === 'Press' ? 2.5 : '',
      'No Of Plates': moduleType === 'Press' ? 4 : '',
      'Cooling Tower': moduleType === 'Press' ? 100 : '',
      'Power Pack': moduleType === 'Press' ? 150 : '',
      'Heater Pack': moduleType === 'Press' ? 120 : '',
      'Cooling Tower Cost': moduleType === 'Press' ? 30 : '',
      Status: 'Active',
    };
    return exportRows(`machinery_${moduleName}_sample`, [row], format);
  }

  previewImport(moduleName: string, dto: ImportPreviewDto) {
    const moduleType = moduleFromSlug(moduleName);
    const rows = parseImportRows(dto.fileName || '', dto.content || '').map((row, index) => {
      const data = mapImportRow(row, moduleType);
      const errors = this.validate(data, true) as string[];
      return { rowNumber: index + 1, data, valid: errors.length === 0, errors };
    });
    return { fileName: dto.fileName || '', rows, total: rows.length, valid: rows.filter((row) => row.valid).length, invalid: rows.filter((row) => !row.valid).length };
  }

  async commitImport(moduleName: string, dto: ImportCommitDto) {
    const moduleType = moduleFromSlug(moduleName);
    let created = 0;
    const errors: string[] = [];
    for (const [index, raw] of (dto.rows || []).entries()) {
      const data = mapImportRow(raw as Record<string, string>, moduleType);
      try {
        await this.create(data);
        created += 1;
      } catch (error) {
        errors.push(`Row ${index + 1}: ${extractError(error)}`);
      }
    }
    return { created, failed: errors.length, errors };
  }

  private buildWhere(query: MachineryQuery): Prisma.MachineryWhereInput {
    const and: Prisma.MachineryWhereInput[] = [];
    const search = clean(query.search);
    const moduleType = clean(query.moduleType);
    if (search) {
      and.push({
        OR: [
          { machineryName: { contains: search, mode: 'insensitive' } },
          { machineryNumber: { contains: search, mode: 'insensitive' } },
          { name: { contains: search, mode: 'insensitive' } },
          { code: { contains: search, mode: 'insensitive' } },
          { platenSize: { contains: search, mode: 'insensitive' } },
        ],
      });
    }
    if (moduleType && moduleTypes.includes(moduleType)) and.push({ moduleType });
    if (clean(query.status)) and.push({ status: normalizeStatus(query.status) });
    return and.length ? { AND: and } : {};
  }

  private validate(dto: MachineryWriteDto, returnErrors = false): Prisma.MachineryCreateInput | string[] {
    const moduleType = moduleTypes.includes(clean(dto.moduleType)) ? clean(dto.moduleType) : 'General';
    const data = {
      moduleType,
      machineryName: clean(dto.machineryName || dto.name),
      machineryNumber: clean(dto.machineryNumber).toUpperCase(),
      eachHp: parseNumber(dto.eachHp, 0),
      hourRate: parseNumber(dto.hourRate, 0),
      name: clean(dto.name) || null,
      code: clean(dto.code).toUpperCase() || null,
      tonnage: optionalNumber(dto.tonnage),
      noOfPlatens: optionalInt(dto.noOfPlatens),
      platenSize: clean(dto.platenSize) || null,
      powerPackHp: optionalNumber(dto.powerPackHp),
      heaterCapacity: optionalNumber(dto.heaterCapacity),
      heaterPerPlaten: optionalNumber(dto.heaterPerPlaten),
      noOfPlates: optionalInt(dto.noOfPlates),
      coolingTower: optionalNumber(dto.coolingTower),
      powerPack: optionalNumber(dto.powerPack),
      heaterPack: optionalNumber(dto.heaterPack),
      coolingTowerCost: optionalNumber(dto.coolingTowerCost),
      status: normalizeStatus(dto.status),
    };
    const errors: string[] = [];
    if (!data.machineryName) errors.push('Machinery name is required.');
    if (!data.machineryNumber) errors.push('Machinery number is required.');
    if (data.eachHp < 0) errors.push('Each HP cannot be negative.');
    if (data.hourRate < 0) errors.push('Hour rate cannot be negative.');
    if (moduleType === 'Injection Moulding') {
      if (!data.name) errors.push('Injection moulding name is required.');
      if (!data.code) errors.push('Injection moulding code is required.');
      if (!data.tonnage || data.tonnage <= 0) errors.push('Tonnage must be greater than zero.');
    }
    if (moduleType === 'Press') {
      const required = [
        ['No of platens', data.noOfPlatens],
        ['Platen size', data.platenSize],
        ['Power pack HP', data.powerPackHp],
        ['Heater capacity', data.heaterCapacity],
        ['Heater per platen', data.heaterPerPlaten],
        ['No of plates', data.noOfPlates],
        ['Cooling tower', data.coolingTower],
        ['Power pack', data.powerPack],
        ['Heater pack', data.heaterPack],
        ['Cooling tower cost', data.coolingTowerCost],
      ];
      for (const [label, value] of required) if (value === null || value === '' || value === undefined) errors.push(`${label} is required.`);
      if ((data.noOfPlatens || 0) <= 0) errors.push('No of platens must be greater than zero.');
      if ((data.noOfPlates || 0) <= 0) errors.push('No of plates must be greater than zero.');
    }
    if (returnErrors) return errors;
    if (errors.length) throw new BadRequestException(errors);
    return data;
  }
}

function moduleFromSlug(value: string) {
  const slug = clean(value).toLowerCase();
  if (slug.includes('inject')) return 'Injection Moulding';
  if (slug.includes('press')) return 'Press';
  return 'General';
}

function mapImportRow(row: Record<string, string>, fallbackModule: string): MachineryWriteDto {
  const data = normalizeImportRow(row);
  return {
    moduleType: data.moduleType || fallbackModule,
    machineryName: data.machineryName,
    machineryNumber: data.machineryNumber,
    eachHp: data.eachHp,
    hourRate: data.hourRate,
    name: data.name,
    code: data.code,
    tonnage: data.tonnage,
    noOfPlatens: data.noOfPlatens,
    platenSize: data.platenSize,
    powerPackHp: data.powerPackHp,
    heaterCapacity: data.heaterCapacity,
    heaterPerPlaten: data.heaterPerPlaten,
    noOfPlates: data.noOfPlates,
    coolingTower: data.coolingTower,
    powerPack: data.powerPack,
    heaterPack: data.heaterPack,
    coolingTowerCost: data.coolingTowerCost,
    status: data.status || 'Active',
  };
}

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

function normalizeStatus(value: unknown) {
  return clean(value).toLowerCase() === 'inactive' ? 'Inactive' : 'Active';
}

function positiveInt(value: unknown, fallback: number) {
  const parsed = Number.parseInt(clean(value), 10);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}

function parseNumber(value: unknown, fallback: number) {
  if (value === null || value === undefined || value === '') return fallback;
  const parsed = Number.parseFloat(String(value));
  return Number.isFinite(parsed) ? parsed : fallback;
}

function optionalNumber(value: unknown) {
  if (value === null || value === undefined || value === '') return null;
  const parsed = Number.parseFloat(String(value));
  return Number.isFinite(parsed) ? parsed : null;
}

function optionalInt(value: unknown) {
  if (value === null || value === undefined || value === '') return null;
  const parsed = Number.parseInt(String(value), 10);
  return Number.isFinite(parsed) ? parsed : null;
}

function extractError(error: unknown) {
  if (error instanceof BadRequestException) {
    const response = error.getResponse() as any;
    return Array.isArray(response.message) ? response.message.join(', ') : response.message || error.message;
  }
  if (error instanceof Error) return error.message;
  return 'Import failed.';
}
