﻿import {
  BadRequestException,
  ConflictException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { In, Repository } from "typeorm";
import { TenantRepositoryAccessor } from "../tenancy/tenant-repository.accessor";
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(private readonly tenantAccessor: TenantRepositoryAccessor) {}

  private get vehiculoRepository(): Repository<Vehiculo> {
    return this.tenantAccessor.repositoryFor(Vehiculo);
  }

  private get vehiculoClienteRepository(): Repository<VehiculoCliente> {
    return this.tenantAccessor.repositoryFor(VehiculoCliente);
  }

  private normalizeRequiredText(value: unknown, fieldLabel: string): string {
    const normalized = String(value ?? "").trim();
    if (!normalized) {
      throw new BadRequestException(`${fieldLabel} es requerido`);
    }
    return normalized;
  }

  private normalizeOptionalText(value: unknown): string | undefined {
    const normalized = String(value ?? "").trim();
    return normalized || undefined;
  }

  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 normalized = query.trim().toLowerCase();
    const placa = `%${query.trim().toUpperCase()}%`;
    const q = `%${normalized}%`;

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

    return this.attachClientes(rows);
  }

  async create(dto: CreateVehiculoDto, userId?: number): Promise<any> {
    const placa = this.normalizeRequiredText(
      dto.placa,
      "La placa",
    ).toUpperCase();
    const marca = this.normalizeRequiredText(dto.marca, "La marca");
    const modelo = this.normalizeRequiredText(dto.modelo, "El modelo");
    const motor = this.normalizeOptionalText(dto.motor);
    const cilindraje = this.normalizeOptionalText(dto.cilindraje);
    const color = this.normalizeOptionalText(dto.color);

    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,
      modelo,
      anio: dto.anio,
      motor,
      cilindraje,
      color,
      tipoVehiculoId: dto.tipoVehiculoId,
      clienteId:
        this.normalizeOptionalText(dto.clienteId) ||
        this.normalizeOptionalText(dto.clienteIds?.[0]) ||
        undefined,
      notas: this.normalizeOptionalText(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`);
    }

    const sanitizedDto: UpdateVehiculoDto = { ...dto };

    if (dto.placa !== undefined) {
      sanitizedDto.placa = this.normalizeRequiredText(
        dto.placa,
        "La placa",
      ).toUpperCase();
    }

    if (dto.marca !== undefined) {
      sanitizedDto.marca = this.normalizeRequiredText(dto.marca, "La marca");
    }

    if (dto.modelo !== undefined) {
      sanitizedDto.modelo = this.normalizeRequiredText(dto.modelo, "El modelo");
    }

    if (dto.motor !== undefined) {
      sanitizedDto.motor = this.normalizeOptionalText(dto.motor);
    }

    if (dto.cilindraje !== undefined) {
      sanitizedDto.cilindraje = this.normalizeOptionalText(dto.cilindraje);
    }

    if (dto.color !== undefined) {
      sanitizedDto.color = this.normalizeOptionalText(dto.color);
    }

    if (dto.notas !== undefined) {
      sanitizedDto.notas = this.normalizeOptionalText(dto.notas);
    }

    if (dto.clienteId !== undefined) {
      sanitizedDto.clienteId = this.normalizeOptionalText(dto.clienteId);
    }

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

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

    await this.vehiculoRepository.save(vehiculo);

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

    return this.findById(id);
  }

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

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