﻿import {
  ConflictException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { In, Repository } from "typeorm";
import { CreateVehiculoDto } from "./dto/create-vehiculo.dto";
import { UpdateVehiculoDto } from "./dto/update-vehiculo.dto";
import { VehiculoCliente } from "./entities/vehiculo-cliente.entity";
import { Vehiculo } from "./entities/vehiculo.entity";

@Injectable()
export class VehiculosService {
  constructor(
    @InjectRepository(Vehiculo)
    private vehiculoRepository: Repository<Vehiculo>,
    @InjectRepository(VehiculoCliente)
    private vehiculoClienteRepository: Repository<VehiculoCliente>,
  ) {}

  private async attachClientes(vehiculos: Vehiculo[]): Promise<any[]> {
    if (vehiculos.length === 0) return [];

    const ids = vehiculos.map((v) => v.id);
    const links = await this.vehiculoClienteRepository.find({
      where: { vehiculoId: In(ids) },
    });

    const map = new Map<number, string[]>();
    for (const link of links) {
      const list = map.get(link.vehiculoId) || [];
      list.push(link.clienteTarjeta);
      map.set(link.vehiculoId, list);
    }

    return vehiculos.map((v) => ({
      ...v,
      clienteIds: map.get(v.id) || (v.clienteId ? [v.clienteId] : []),
    }));
  }

  private async syncClienteLinks(
    vehiculoId: number,
    clienteIds?: string[],
    clienteIdLegacy?: string,
  ) {
    const normalized = Array.from(
      new Set(
        (clienteIds || []).map((x) => String(x || "").trim()).filter(Boolean),
      ),
    );
    if (normalized.length === 0 && clienteIdLegacy) {
      normalized.push(clienteIdLegacy);
    }

    await this.vehiculoClienteRepository.delete({ vehiculoId });

    if (normalized.length > 0) {
      await this.vehiculoClienteRepository.save(
        normalized.map((tarjeta) =>
          this.vehiculoClienteRepository.create({
            vehiculoId,
            clienteTarjeta: tarjeta,
          }),
        ),
      );
    }

    return normalized;
  }

  async findAll(clienteId?: string) {
    const where: any = {};
    if (clienteId) {
      const links = await this.vehiculoClienteRepository.find({
        where: { clienteTarjeta: clienteId },
      });
      const ids = links.map((l) => l.vehiculoId);
      if (ids.length === 0) {
        return [];
      }
      where.id = In(ids);
    }

    const rows = await this.vehiculoRepository.find({
      where,
      relations: ["tipoVehiculo"],
      order: { placa: "ASC" },
    });

    return this.attachClientes(rows);
  }

  async findByPlaca(placa: string): Promise<any> {
    const vehiculo = await this.vehiculoRepository.findOne({
      where: { placa: placa.toUpperCase() },
      relations: ["tipoVehiculo"],
    });

    if (!vehiculo) {
      throw new NotFoundException(`Vehículo con placa ${placa} no encontrado`);
    }

    const [withClientes] = await this.attachClientes([vehiculo]);
    return withClientes;
  }

  async findById(id: number): Promise<any> {
    const vehiculo = await this.vehiculoRepository.findOne({
      where: { id },
      relations: ["tipoVehiculo"],
    });

    if (!vehiculo) {
      throw new NotFoundException(`Vehículo ${id} no encontrado`);
    }

    const [withClientes] = await this.attachClientes([vehiculo]);
    return withClientes;
  }

  async search(query: string, limit = 20) {
    if (!query || query.length < 2) {
      return [];
    }

    const rows = await this.vehiculoRepository
      .createQueryBuilder("vehiculo")
      .leftJoinAndSelect("vehiculo.tipoVehiculo", "tipo")
      .where("vehiculo.placa LIKE :q", { q: `%${query.toUpperCase()}%` })
      .orWhere("vehiculo.marca LIKE :q", { q: `%${query}%` })
      .orWhere("vehiculo.modelo LIKE :q", { q: `%${query}%` })
      .orderBy("vehiculo.placa", "ASC")
      .limit(limit)
      .getMany();

    return this.attachClientes(rows);
  }

  async create(dto: CreateVehiculoDto, userId?: number): Promise<any> {
    const placa = dto.placa.toUpperCase();

    const existing = await this.vehiculoRepository.findOne({
      where: { placa },
    });
    if (existing) {
      throw new ConflictException(`Ya existe un vehículo con placa ${placa}`);
    }

    const vehiculo = this.vehiculoRepository.create({
      placa,
      marca: dto.marca,
      modelo: dto.modelo,
      anio: dto.anio,
      motor: dto.motor,
      cilindraje: dto.cilindraje,
      color: dto.color,
      tipoVehiculoId: dto.tipoVehiculoId,
      clienteId: dto.clienteId || dto.clienteIds?.[0] || undefined,
      notas: dto.notas,
      createdBy: userId,
      updatedBy: userId,
    });

    const saved = await this.vehiculoRepository.save(vehiculo);
    const normalizedClienteIds = await this.syncClienteLinks(
      saved.id,
      dto.clienteIds,
      dto.clienteId,
    );

    return {
      ...(await this.findById(saved.id)),
      clienteIds: normalizedClienteIds,
    };
  }

  async update(
    id: number,
    dto: UpdateVehiculoDto,
    userId?: number,
  ): Promise<any> {
    const vehiculo = await this.vehiculoRepository.findOne({ where: { id } });
    if (!vehiculo) {
      throw new NotFoundException(`Vehículo ${id} no encontrado`);
    }

    if (dto.placa && dto.placa.toUpperCase() !== vehiculo.placa) {
      const existing = await this.vehiculoRepository.findOne({
        where: { placa: dto.placa.toUpperCase() },
      });
      if (existing) {
        throw new ConflictException(
          `Ya existe un vehículo con placa ${dto.placa}`,
        );
      }
      dto.placa = dto.placa.toUpperCase();
    }

    Object.assign(vehiculo, dto, {
      clienteId: dto.clienteId || dto.clienteIds?.[0] || vehiculo.clienteId,
      updatedBy: userId,
    });

    await this.vehiculoRepository.save(vehiculo);

    if (dto.clienteIds !== undefined || dto.clienteId !== undefined) {
      await this.syncClienteLinks(id, dto.clienteIds, dto.clienteId);
    }

    return this.findById(id);
  }

  async findOrCreate(dto: CreateVehiculoDto, userId?: number): Promise<any> {
    const placa = dto.placa.toUpperCase();

    try {
      return await this.findByPlaca(placa);
    } catch {
      return this.create(dto, userId);
    }
  }
}
