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

import { BookingsRepository } from '../bookings/bookings.repository';
import { CreateLastMileDto } from './dto/create-last-mile.dto';
import { UpdateLastMileDto } from './dto/update-last-mile.dto';
import { LastMile, LastMileStatus } from './entities/last-mile.entity';
import { LastMileRepository } from './last-mile.repository';

type LastMileListFilter = {
  status?: LastMileStatus;
  bookingId?: string;
  vehicleId?: string;
  page?: number;
  pageSize?: number;
  sortBy?: string;
  sortOrder?: string;
};

const SORTABLE_FIELDS: (keyof LastMile)[] = [
  'status',
  'advancedPayment',
  'remainingPayment',
  'createdAt',
];

@Injectable()
export class LastMileService {
  constructor(
    private readonly lastMileRepository: LastMileRepository,
    private readonly bookingsRepository: BookingsRepository,
  ) {}

  async acceptBooking(bookingReference: string): Promise<LastMile> {
    const booking = await this.bookingsRepository.findByReference(bookingReference);

    if (!booking) {
      throw new NotFoundException(`Booking ${bookingReference} not found`);
    }

    if (booking.paymentStatus !== 'PAID') {
      throw new BadRequestException(
        `Booking ${bookingReference} is not paid (payment status: ${booking.paymentStatus})`,
      );
    }

    return this.create({
      bookingId: booking.id,
      advancedPayment: booking.totalAmount,
    });
  }

  async findAll(filter: LastMileListFilter = {}): Promise<{
    data: LastMile[];
    meta: { total: number; page: number; pageSize: number; totalPages: number };
  }> {
    const page = filter.page ?? 1;
    const pageSize = filter.pageSize ?? 50;
    const sortBy = SORTABLE_FIELDS.includes(filter.sortBy as keyof LastMile)
      ? (filter.sortBy as keyof LastMile)
      : 'createdAt';
    const sortOrder = filter.sortOrder?.toUpperCase() === 'ASC' ? 'ASC' : 'DESC';

    const where: FindOptionsWhere<LastMile> = {};
    if (filter.status) where.status = filter.status;
    if (filter.bookingId) where.bookingId = filter.bookingId;
    if (filter.vehicleId) where.vehicleId = filter.vehicleId;

    const [data, total] = await this.lastMileRepository.findAndCount({
      where,
      relations: {
        booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true },
        vehicle: true,
      },
      order: { [sortBy]: sortOrder },
      skip: (page - 1) * pageSize,
      take: pageSize,
    });

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

  async findById(id: string): Promise<LastMile> {
    const record = await this.lastMileRepository.findById(id, {
      relations: {
        booking: { company: true, serviceType: true, originYard: true, destinationYard: true, cargoType: true },
        vehicle: true,
      },
    });

    if (!record) {
      throw new NotFoundException(`Last-mile record ${id} not found`);
    }

    return record;
  }

  async create(dto: CreateLastMileDto): Promise<LastMile> {
    return this.lastMileRepository.create({
      bookingId: dto.bookingId,
      status: dto.status ?? 'PAYMENT_PENDING',
      advancedPayment: dto.advancedPayment ?? 0,
      remainingPayment: dto.remainingPayment ?? 0,
      estimatedKm: dto.estimatedKm ?? null,
      exactKm: dto.exactKm ?? null,
      vehicleId: dto.vehicleId ?? null,
    });
  }

  async update(id: string, dto: UpdateLastMileDto): Promise<LastMile> {
    await this.findById(id);

    const updated = await this.lastMileRepository.update(id, {
      ...(dto.bookingId !== undefined ? { bookingId: dto.bookingId } : {}),
      ...(dto.status !== undefined ? { status: dto.status } : {}),
      ...(dto.advancedPayment !== undefined ? { advancedPayment: dto.advancedPayment } : {}),
      ...(dto.remainingPayment !== undefined ? { remainingPayment: dto.remainingPayment } : {}),
      ...(dto.estimatedKm !== undefined ? { estimatedKm: dto.estimatedKm } : {}),
      ...(dto.exactKm !== undefined ? { exactKm: dto.exactKm } : {}),
      ...(dto.vehicleId !== undefined ? { vehicleId: dto.vehicleId } : {}),
    });

    if (!updated) {
      throw new NotFoundException(`Last-mile record ${id} not found`);
    }

    return updated;
  }

  async remove(id: string): Promise<void> {
    await this.findById(id);
    await this.lastMileRepository.softDelete(id);
  }
}
