import { Injectable, ConflictException, NotFoundException } from '@nestjs/common';
import { VehicleRepository } from './repositories/vehicle.repository';
import { VehicleMediaRepository } from './repositories/vehicle-media.repository';
import { MediaService } from '../../common/media/media.service';
import { CreateVehicleDto, UpdateVehicleDto } from './dto';
import { VehicleEntity, VehicleStatus } from '../../entities/vehicle.entity';
import { DestinationEntity } from '../../entities/destination.entity';
import { VehicleTypeEntity } from '../../entities/vehicle-type.entity';
import { TransportServiceEntity } from '../../entities/transport-service.entity';
import { TransportPricingEntity } from '../../entities/transport-pricing.entity';
import type { FindOptionsWhere } from 'typeorm';
import { serviceMessages } from '../../common/constants/messages';
import { LOOKUP_MAX } from '../../common/constants/lookup-limits';

const MSG = serviceMessages('Vehicle');

@Injectable()
export class VehicleService {
  constructor(
    private readonly repo: VehicleRepository,
    private readonly mediaRepo: VehicleMediaRepository,
    private readonly media: MediaService,
  ) {}

  async findAll(
    query?: { page?: number; limit?: number; search?: string },
    filters?: { status?: string; type?: string; supplierId?: string; destinationId?: string },
  ) {
    const where: FindOptionsWhere<VehicleEntity> = {};
    if (filters?.status) where.status = filters.status as VehicleEntity['status'];
    if (filters?.type) where.type = filters.type;
    if (filters?.supplierId) where.supplier_id = filters.supplierId;
    if (filters?.destinationId) where.destination_id = filters.destinationId;

    return this.repo.findPaginated({
      page: query?.page,
      limit: query?.limit,
      search: query?.search,
      searchColumns: ['vehicle_no', 'type', 'company', 'model'],
      relations: ['destination', 'supplier', 'assigned_driver'],
      order: { vehicle_no: 'ASC' },
      where,
    });
  }

  async findAllForLookup() {
    return this.repo.find({
      where: { is_active: true },
      select: {
        id: true,
        vehicle_no: true,
        type: true,
        capacity: true,
        capping: true,
        supplier_id: true,
        assigned_driver_id: true,
        destination_id: true,
      },
      order: { vehicle_no: 'ASC' },
      take: LOOKUP_MAX,
    });
  }

  async findById(id: string) {
    const item = await this.repo.findByIdWithRelations(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);
    return { ...item, media: await this.media.list(this.mediaRepo, id) };
  }

  async create(dto: CreateVehicleDto) {
    if (dto.vehicle_no) {
      const existing = await this.repo.findByVehicleNo(dto.vehicle_no);
      if (existing) throw new ConflictException(MSG.ALREADY_EXISTS('registration number'));
    }
    const media = await this.media.prepare(this.mediaRepo, 'vehicles', dto.media);

    const item = await this.repo.save({
      destination_id: dto.destination_id || null,
      supplier_id: dto.supplier_id || null,
      assigned_driver_id: dto.assigned_driver_id || null,
      vehicle_no: dto.vehicle_no || null,
      type: dto.type || null,
      company: dto.company || null,
      model: dto.model || null,
      year: dto.year ?? null,
      capacity: dto.capacity ?? null,
      capping: dto.capping ?? null,
      fuel_type: dto.fuel_type || null,
      color: dto.color || null,
      insurance_expiry: dto.insurance_expiry || null,
      features: dto.features || null,
      status: dto.status || VehicleStatus.AVAILABLE,
      notes: dto.notes || null,
      is_active: dto.is_active ?? true,
    });
    await this.media.sync(this.mediaRepo, item.id, media);

    // Auto-create transport pricing for this vehicle type on all routes in the destination
    try {
      const tenantId = this.repo.currentTenantId();
      const userId = this.repo.currentUserId();
      const manager = this.repo.manager;

      const dest = await manager.findOne(DestinationEntity, {
        where: { id: dto.destination_id },
      });
      const currencyId = dest?.currency_id;

      if (currencyId && dto.type) {
        // Resolve the name string on the vehicle to the FK on the
        // vehicle_types master so transport_pricing can be seeded
        // against vehicle_type_id (FK). vehicle.type stays a string
        // on the vehicle entity itself — that's a separate refactor
        // — so we look up by name here.
        const vt = await manager.findOne(VehicleTypeEntity, {
          where: { tenant_id: tenantId, name: dto.type, is_deleted: false },
        });
        const vehicleTypeId = vt?.id || null;
        if (!vehicleTypeId) {
          // Vehicle's type isn't in the master yet — skip the pricing
          // seed; ops can create the master entry later and the
          // backfillPricingForType path will fan out then.
        } else {
          // Find all active routes for this destination that don't have this vehicle type priced
          const routes = await manager.find(TransportServiceEntity, {
            where: {
              destination_id: dto.destination_id,
              tenant_id: tenantId,
              is_deleted: false,
              is_active: true,
            },
          });

          for (const route of routes) {
            const exists = await manager.findOne(TransportPricingEntity, {
              where: {
                transport_service_id: route.id,
                vehicle_type_id: vehicleTypeId,
                currency_id: currencyId,
                tenant_id: tenantId,
                is_deleted: false,
              },
            });
            if (!exists) {
              await manager.save(TransportPricingEntity, {
                tenant_id: tenantId,
                destination_id: dto.destination_id,
                transport_service_id: route.id,
                category: route.category,
                vehicle_type_id: vehicleTypeId,
                currency_id: currencyId,
                created_by: userId,
                updated_by: userId,
              });
            }
          }
        }
      }
    } catch {
      /* Don't fail vehicle creation */
    }

    return { id: item.id, message: MSG.CREATED };
  }

  async update(id: string, dto: UpdateVehicleDto) {
    const item = await this.repo.findById(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);

    if (dto.vehicle_no && dto.vehicle_no !== item.vehicle_no) {
      const existing = await this.repo.findByVehicleNo(dto.vehicle_no);
      if (existing && existing.id !== id)
        throw new ConflictException(MSG.ALREADY_EXISTS('registration number'));
    }
    const media = await this.media.prepare(this.mediaRepo, 'vehicles', dto.media, id);

    const updateData: Record<string, unknown> = {};
    const fields = [
      'destination_id',
      'supplier_id',
      'assigned_driver_id',
      'vehicle_no',
      'type',
      'company',
      'model',
      'year',
      'capacity',
      'capping',
      'fuel_type',
      'color',
      'insurance_expiry',
      'features',
      'status',
      'notes',
      'is_active',
    ] as const;
    const dtoRecord = dto as Record<string, unknown>;
    for (const field of fields) {
      if (dtoRecord[field] !== undefined) updateData[field] = dtoRecord[field];
    }

    await this.repo.update(id, updateData);
    await this.media.sync(this.mediaRepo, id, media);
    return { id, message: MSG.UPDATED };
  }

  /**
   * Assign/unassign driver — inline action from list page.
   * Pass null to unassign.
   */
  async assignDriver(id: string, driverId: string | null) {
    const item = await this.repo.findById(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);

    await this.repo.update(id, {
      assigned_driver_id: driverId,
      status: driverId ? VehicleStatus.ASSIGNED : VehicleStatus.AVAILABLE,
    });

    return {
      id,
      message: driverId ? 'Driver assigned successfully' : 'Driver unassigned successfully',
    };
  }

  async remove(id: string) {
    const item = await this.repo.findById(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);

    await this.repo.softDelete(id);
    return { message: MSG.DELETED };
  }
}
