import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { FindManyOptions, ILike, QueryFailedError } from 'typeorm';

import { CreateWarehouseDto } from './dto/create-warehouse.dto';
import { FilterWarehouseDto } from './dto/filter-warehouse.dto';
import { UpdateWarehouseDto } from './dto/update-warehouse.dto';
import { Warehouse } from './entities/warehouse.entity';
import { WarehousesRepository } from './warehouses.repository';

@Injectable()
export class WarehousesService {
  constructor(private readonly warehousesRepository: WarehousesRepository) {}

  async findAll(filter: FilterWarehouseDto): Promise<Warehouse[]> {
    const where: FindManyOptions<Warehouse>['where'] = {
      ...(filter.type ? { type: filter.type } : {}),
      ...(filter.stationId ? { stationId: filter.stationId } : {}),
      ...(filter.status ? { status: filter.status } : {}),
    };

    const search = filter.search?.trim();
    const whereClauses = search
      ? [
          { ...where, name: ILike(`%${search}%`) },
          { ...where, code: ILike(`%${search}%`) },
          { ...where, locationName: ILike(`%${search}%`) },
        ]
      : where;

    return this.warehousesRepository.findAll({
      where: whereClauses,
      relations: { facility: true },
      order: { code: 'ASC' },
    });
  }

  async findById(id: string): Promise<Warehouse> {
    const warehouse = await this.warehousesRepository.findById(id, {
      relations: { facility: true, yards: { zones: true } },
    });

    if (!warehouse) {
      throw new NotFoundException(`Warehouse ${id} not found`);
    }

    return warehouse;
  }

  async create(dto: CreateWarehouseDto): Promise<Warehouse> {
    await this.assertCodeUnique(dto.code.trim());

    try {
      return await this.warehousesRepository.create({
        name: dto.name.trim(),
        code: dto.code.trim(),
        type: dto.type,
        stationId: dto.stationId ?? null,
        facilityId: dto.facilityId ?? null,
        locationName: dto.locationName?.trim() ?? null,
        capacityWeight: dto.capacityWeight ?? null,
        capacityContainers: dto.capacityContainers ?? null,
        maxWeight: dto.maxWeight ?? dto.capacityWeight ?? null,
        maxVolume: dto.maxVolume ?? null,
        currentWeight: 0,
        currentContainers: 0,
        currentVolume: 0,
        status: 'ACTIVE',
        isActive: true,
      });
    } catch (error) {
      this.mapDbError(error);
    }
  }

  async update(id: string, dto: UpdateWarehouseDto): Promise<Warehouse> {
    const existing = await this.findById(id);

    if (dto.code && dto.code.trim() !== existing.code) {
      await this.assertCodeUnique(dto.code.trim(), id);
    }

    const status = dto.status ?? existing.status;

    let updated;
    try {
      updated = await this.warehousesRepository.update(id, {
        name: dto.name?.trim() ?? existing.name,
        code: dto.code?.trim() ?? existing.code,
        type: dto.type ?? existing.type,
        stationId: dto.stationId ?? existing.stationId,
        facilityId: dto.facilityId ?? existing.facilityId,
        locationName: dto.locationName?.trim() ?? existing.locationName,
        capacityWeight: dto.capacityWeight ?? existing.capacityWeight,
        capacityContainers: dto.capacityContainers ?? existing.capacityContainers,
        maxWeight: dto.maxWeight ?? existing.maxWeight,
        maxVolume: dto.maxVolume ?? existing.maxVolume,
        status,
        isActive: status === 'ACTIVE',
      });
    } catch (error) {
      this.mapDbError(error);
    }

    if (!updated) {
      throw new NotFoundException(`Warehouse ${id} not found`);
    }

    return this.findById(id);
  }

  /** Map low-level DB errors (FK / length / etc.) to a clean 400 instead of a 500. */
  private mapDbError(error: unknown): never {
    if (error instanceof QueryFailedError) {
      const driver = (error as QueryFailedError & { driverError?: { code?: string; detail?: string } }).driverError;
      if (driver?.code === '23503') {
        throw new BadRequestException('Selected facility does not exist.');
      }
      if (driver?.code === '22001') {
        throw new BadRequestException('A field is too long (code max 40, name max 160 characters).');
      }
      throw new BadRequestException(driver?.detail ?? error.message ?? 'Invalid warehouse data.');
    }
    throw error as Error;
  }

  private async assertCodeUnique(code: string, ignoreId?: string): Promise<void> {
    const [existing] = await this.warehousesRepository.findAll({ where: { code } });

    if (existing && existing.id !== ignoreId) {
      throw new ConflictException(`Warehouse code ${code} already exists`);
    }
  }
}
