import { Injectable, NotFoundException, ConflictException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateDriverDto } from './dto/create-driver.dto';
import { UpdateDriverDto } from './dto/update-driver.dto';
import { Driver, DriverStatus } from './entities/driver.entity';
import { FleetHistoryService } from '../fleet-history/fleet-history.service';
import { FleetEventType } from '../fleet-history/entities/fleet-event.entity';
import { FilesService } from '../files/files.service';

/** Resource + code the driver-documents upload area is stored under. */
const DRIVER_DOCS_RESOURCE = 'driver';
const DRIVER_DOCS_CODE = 'driver_docs';

@Injectable()
export class DriversService {
  constructor(
    @InjectRepository(Driver)
    private readonly driverRepo: Repository<Driver>,
    private readonly history: FleetHistoryService,
    private readonly filesService: FilesService,
  ) {}

  /** Upload one or more driver documents (code "driver_docs"). */
  async uploadDocuments(driverId: string, files: Express.Multer.File[]) {
    const driver = await this.driverRepo.findOneBy({ id: driverId });
    if (!driver) throw new NotFoundException(`Driver ${driverId} not found`);
    if (!files?.length) throw new BadRequestException('No files provided');
    return Promise.all(
      files.map((file) =>
        this.filesService.upload({
          resourceId: driverId,
          resource: DRIVER_DOCS_RESOURCE,
          code: DRIVER_DOCS_CODE,
          file,
        }),
      ),
    );
  }

  /** List a driver's uploaded documents (code "driver_docs"). */
  async listDocuments(driverId: string) {
    const all = await this.filesService.findByResource(driverId, DRIVER_DOCS_RESOURCE);
    return all.filter((f) => f.code === DRIVER_DOCS_CODE);
  }

  /** Delete a single driver document by file id. */
  async removeDocument(fileId: string): Promise<void> {
    await this.filesService.remove(fileId);
  }

  async create(dto: CreateDriverDto): Promise<Driver> {
    if (dto.faydaVerified !== true) {
      throw new BadRequestException(
        'Driver identity must be verified with Fayda before saving',
      );
    }

    const existing = await this.driverRepo.findOne({
      where: [
        { licenseNumber: dto.licenseNumber },
        { email: dto.email },
        { phoneNumber: dto.phoneNumber },
      ],
    });

    if (existing) {
      if (existing.licenseNumber === dto.licenseNumber) {
        throw new ConflictException(`Driver with license number ${dto.licenseNumber} already exists`);
      }
      if (existing.email === dto.email) {
        throw new ConflictException(`Driver with email ${dto.email} already exists`);
      }
      if (existing.phoneNumber === dto.phoneNumber) {
        throw new ConflictException(`Driver with phone number ${dto.phoneNumber} already exists`);
      }
    }

    if (dto.faydaSub) {
      const dupe = await this.driverRepo.findOne({
        where: { faydaSub: dto.faydaSub },
      });
      if (dupe) {
        throw new ConflictException(
          'A driver is already registered for this Fayda identity',
        );
      }
    }

    const driver = this.driverRepo.create(dto);
    const saved = await this.driverRepo.save(driver);

    await this.history.record({
      eventType: FleetEventType.DRIVER_REGISTERED,
      driverId: saved.id,
      label: `${saved.firstName ?? ''} ${saved.lastName ?? ''}`.trim() || null,
      toValue: saved.status ?? null,
    });

    return saved;
  }

  async findAll(query: {
    search?: string;
    status?: DriverStatus | string;
    page?: number;
    limit?: number;
    sortBy?: string;
    sortOrder?: 'ASC' | 'DESC';
  } = {}): Promise<Driver[]> {
    const qb = this.driverRepo.createQueryBuilder('d');

    if (query.search) {
      qb.where(
        '(d.firstName ILIKE :search OR d.lastName ILIKE :search OR d.email ILIKE :search OR d.licenseNumber ILIKE :search OR d.phoneNumber ILIKE :search)',
        { search: `%${query.search}%` },
      );
    }

    if (query.status) {
      qb.andWhere('d.status = :status', { status: query.status });
    }

    const sortBy = query.sortBy && ['firstName', 'lastName', 'status', 'createdAt'].includes(query.sortBy)
      ? query.sortBy
      : 'createdAt';
    const sortOrder = (query.sortOrder ?? 'DESC').toUpperCase();

    return qb
      .orderBy(`d.${sortBy}`, sortOrder as 'ASC' | 'DESC')
      .getMany();
  }

  async findById(id: string): Promise<Driver> {
    const driver = await this.driverRepo.findOne({ where: { id } });
    if (!driver) {
      throw new NotFoundException(`Driver ${id} not found`);
    }
    return driver;
  }

  async update(id: string, dto: UpdateDriverDto): Promise<Driver> {
    const driver = await this.findById(id);

    if (dto.licenseNumber && dto.licenseNumber !== driver.licenseNumber) {
      const existing = await this.driverRepo.findOne({
        where: { licenseNumber: dto.licenseNumber },
      });
      if (existing) {
        throw new ConflictException(`Driver with license number ${dto.licenseNumber} already exists`);
      }
    }

    if (dto.email && dto.email !== driver.email) {
      const existing = await this.driverRepo.findOne({
        where: { email: dto.email },
      });
      if (existing) {
        throw new ConflictException(`Driver with email ${dto.email} already exists`);
      }
    }

    if (dto.phoneNumber && dto.phoneNumber !== driver.phoneNumber) {
      const existing = await this.driverRepo.findOne({
        where: { phoneNumber: dto.phoneNumber },
      });
      if (existing) {
        throw new ConflictException(`Driver with phone number ${dto.phoneNumber} already exists`);
      }
    }

    if (dto.faydaSub && dto.faydaSub !== driver.faydaSub) {
      const dupe = await this.driverRepo.findOne({
        where: { faydaSub: dto.faydaSub },
      });
      if (dupe) {
        throw new ConflictException(
          'A driver is already registered for this Fayda identity',
        );
      }
    }

    Object.assign(driver, dto);

    if (driver.faydaVerified !== true) {
      throw new BadRequestException(
        'Driver identity must be verified with Fayda before saving',
      );
    }

    return this.driverRepo.save(driver);
  }

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