import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { CreateRateDto } from '../dto/create-rate.dto';
import { UpdateRateDto } from '../dto/update-rate.dto';
import { Rate } from '../entities/rate.entity';
import { deriveRateType } from '../entities/rate-type.util';
import { IRatesRepository, RATES_REPOSITORY } from '../interfaces/rates.repository.interface';

@Injectable()
export class RatesService {
  constructor(
    @Inject(RATES_REPOSITORY)
    private readonly repository: IRatesRepository,
  ) {}

  /** List rates with pagination. */
  async findAll(filter: {
    status?: string;
    rateType?: string;
    page?: number;
    pageSize?: number;
  }): Promise<{ data: Rate[]; meta: { total: number; page: number; pageSize: number; totalPages: number } }> {
    const page = filter.page ?? 1;
    const pageSize = filter.pageSize ?? 20;
    const where: Record<string, unknown> = {};
    if (filter.status) where.status = filter.status;
    if (filter.rateType) where.rateType = filter.rateType;

    const [data, total] = await this.repository.findAndCount({
      where,
      order: { effectiveFrom: 'DESC' },
      skip: (page - 1) * pageSize,
      take: pageSize,
    });
    return { data, meta: { total, page, pageSize, totalPages: Math.max(1, Math.ceil(total / pageSize)) } };
  }

  /** Return all currently LIVE rates. */
  async findLiveRates(): Promise<Rate[]> {
    return this.repository.findLiveRates();
  }

  /** Get a rate by ID. */
  async findById(id: string): Promise<Rate> {
    const entity = await this.repository.findById(id);
    if (!entity) throw new NotFoundException(`Rate ${id} not found`);
    return entity;
  }

  /** Create a rate in DRAFT status. */
  async create(dto: CreateRateDto, proposedByStaffId: string): Promise<Rate> {
    const appliesTo = dto.appliesTo as Rate['appliesTo'];
    const trigger = dto.trigger as Rate['trigger'];
    // Surcharges (trigger ≠ ALWAYS) carry no direction/scope — clear them so
    // the engine never accidentally narrows a surcharge by container/direction.
    const isSurcharge = trigger !== 'ALWAYS';
    const containerTypeId = isSurcharge ? null : (dto.containerTypeId ?? null);
    const cargoTypeId = isSurcharge ? null : (dto.cargoTypeId ?? null);
    const tradeDirection = isSurcharge ? null : (dto.tradeDirection ?? null);

    return this.repository.create({
      appliesTo,
      trigger,
      rateType: deriveRateType({
        appliesTo,
        trigger,
        tradeDirection,
        isBulk: Boolean(cargoTypeId),
      }),
      containerTypeId,
      cargoTypeId,
      tradeDirection,
      currency: dto.currency ?? 'USD',
      rateValue: dto.rateValue,
      rateUnit: dto.rateUnit as Rate['rateUnit'],
      status: 'DRAFT',
      proposedByStaffId,
      effectiveFrom: new Date(dto.effectiveFrom),
      effectiveTo: dto.effectiveTo ? new Date(dto.effectiveTo) : undefined,
    });
  }

  /** Update a DRAFT rate. */
  async update(id: string, dto: UpdateRateDto): Promise<Rate> {
    const existing = await this.findById(id);
    if (existing.status !== 'DRAFT') {
      throw new BadRequestException('Only DRAFT rates can be updated');
    }
    const updates: Partial<Rate> = {};

    const appliesTo = (dto.appliesTo as Rate['appliesTo']) ?? existing.appliesTo;
    const trigger = (dto.trigger as Rate['trigger']) ?? existing.trigger;
    const isSurcharge = trigger !== 'ALWAYS';

    if (dto.appliesTo) updates.appliesTo = appliesTo;
    if (dto.trigger) updates.trigger = trigger;

    const containerTypeId = isSurcharge
      ? null
      : dto.containerTypeId !== undefined
        ? dto.containerTypeId
        : existing.containerTypeId;
    const cargoTypeId = isSurcharge
      ? null
      : dto.cargoTypeId !== undefined
        ? dto.cargoTypeId
        : existing.cargoTypeId;
    const tradeDirection = isSurcharge
      ? null
      : dto.tradeDirection !== undefined
        ? dto.tradeDirection
        : existing.tradeDirection;

    updates.containerTypeId = containerTypeId;
    updates.cargoTypeId = cargoTypeId;
    updates.tradeDirection = tradeDirection;
    // Keep the derived rateType in sync with whatever changed.
    updates.rateType = deriveRateType({
      appliesTo,
      trigger,
      tradeDirection,
      isBulk: Boolean(cargoTypeId),
    });

    updates.currency = dto.currency ?? existing.currency ?? 'USD';
    if (dto.rateValue !== undefined) updates.rateValue = dto.rateValue;
    if (dto.rateUnit) updates.rateUnit = dto.rateUnit as Rate['rateUnit'];
    if (dto.effectiveFrom) updates.effectiveFrom = new Date(dto.effectiveFrom);
    if (dto.effectiveTo) updates.effectiveTo = new Date(dto.effectiveTo);
    const updated = await this.repository.update(id, updates);
    if (!updated) throw new NotFoundException(`Rate ${id} not found`);
    return updated;
  }

  /** Submit a DRAFT rate for CEO approval. */
  async submitForApproval(id: string): Promise<Rate> {
    const rate = await this.findById(id);
    if (rate.status !== 'DRAFT') {
      throw new BadRequestException('Only DRAFT rates can be submitted for approval');
    }
    const updated = await this.repository.update(id, { status: 'PENDING_APPROVAL' });
    return updated!;
  }

  /** CEO approves a rate — moves to LIVE. */
  async approve(id: string, approverUserId: string): Promise<Rate> {
    const rate = await this.findById(id);
    if (rate.status !== 'PENDING_APPROVAL') {
      throw new BadRequestException('Only PENDING_APPROVAL rates can be approved');
    }
    const updated = await this.repository.update(id, {
      status: 'LIVE',
      approvedByCeoId: approverUserId,
      approvedAt: new Date(),
    });
    return updated!;
  }

  /** Soft-delete a rate. */
  async remove(id: string): Promise<void> {
    await this.findById(id);
    await this.repository.softDelete(id);
  }
}
