import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';

import { CreateWarehouseYardDto } from './dto/create-warehouse-yard.dto';
import { UpdateWarehouseYardDto } from './dto/update-warehouse-yard.dto';
import { WarehouseYard } from './entities/warehouse-yard.entity';
import { WarehouseYardsRepository } from './warehouse-yards.repository';
import { WarehousesService } from './warehouses.service';

@Injectable()
export class WarehouseYardsService {
  constructor(
    private readonly yardsRepository: WarehouseYardsRepository,
    private readonly warehousesService: WarehousesService,
  ) {}

  findAll(): Promise<WarehouseYard[]> {
    return this.yardsRepository.findAll({
      relations: { warehouse: true, zones: true },
      order: { code: 'ASC' },
    });
  }

  findByWarehouse(warehouseId: string): Promise<WarehouseYard[]> {
    return this.yardsRepository.findAll({
      where: { warehouseId },
      relations: { zones: true },
      order: { code: 'ASC' },
    });
  }

  async findById(id: string): Promise<WarehouseYard> {
    const yard = await this.yardsRepository.findById(id, {
      relations: { warehouse: true, zones: true },
    });

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

    return yard;
  }

  async create(warehouseId: string, dto: CreateWarehouseYardDto): Promise<WarehouseYard> {
    // Ensure the parent warehouse exists.
    await this.warehousesService.findById(warehouseId);
    await this.assertCodeUnique(warehouseId, dto.code.trim());
    await this.assertCapacityWithinWarehouse(warehouseId, dto.capacityWeight ?? null, dto.capacityContainers ?? null);

    return this.yardsRepository.create({
      warehouseId,
      name: dto.name.trim(),
      code: dto.code.trim(),
      type: dto.type,
      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,
    });
  }

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

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

    const newCapacityWeight = dto.capacityWeight ?? existing.capacityWeight ?? null;
    const newCapacityContainers = dto.capacityContainers ?? existing.capacityContainers ?? null;

    // Validate updated capacity doesn't exceed warehouse limits
    if (newCapacityWeight !== (existing.capacityWeight ?? null) || newCapacityContainers !== (existing.capacityContainers ?? null)) {
      await this.assertCapacityWithinWarehouse(existing.warehouseId, newCapacityWeight, newCapacityContainers, id);
    }

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

    const updated = await this.yardsRepository.update(id, {
      name: dto.name?.trim() ?? existing.name,
      code: dto.code?.trim() ?? existing.code,
      type: dto.type ?? existing.type,
      capacityWeight: newCapacityWeight,
      capacityContainers: newCapacityContainers,
      maxWeight: dto.maxWeight ?? existing.maxWeight,
      maxVolume: dto.maxVolume ?? existing.maxVolume,
      status,
      isActive: status === 'ACTIVE',
    });

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

    return this.findById(id);
  }

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

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

  private async assertCapacityWithinWarehouse(
    warehouseId: string,
    newCapacityWeight: number | null,
    newCapacityContainers: number | null,
    excludeYardId?: string,
  ): Promise<void> {
    const warehouse = await this.warehousesService.findById(warehouseId);
    const yards = await this.findByWarehouse(warehouseId);

    // Sum existing yard capacities, excluding the yard being updated if provided
    const otherYards = excludeYardId ? yards.filter((y) => y.id !== excludeYardId) : yards;
    const totalExistingWeight = otherYards.reduce((sum, y) => sum + (y.capacityWeight ?? 0), 0);
    const totalExistingContainers = otherYards.reduce((sum, y) => sum + (y.capacityContainers ?? 0), 0);

    // Check weight capacity
    if (newCapacityWeight !== null && warehouse.capacityWeight != null) {
      const totalWeight = totalExistingWeight + newCapacityWeight;
      if (totalWeight > warehouse.capacityWeight) {
        throw new BadRequestException(
          `Total yard weight capacity (${totalWeight}t) exceeds warehouse limit (${warehouse.capacityWeight}t)`,
        );
      }
    }

    // Check container capacity
    if (newCapacityContainers !== null && warehouse.capacityContainers != null) {
      const totalContainers = totalExistingContainers + newCapacityContainers;
      if (totalContainers > warehouse.capacityContainers) {
        throw new BadRequestException(
          `Total yard container capacity (${totalContainers}) exceeds warehouse limit (${warehouse.capacityContainers})`,
        );
      }
    }
  }
}
