import { Injectable, NotFoundException } from '@nestjs/common';
import { DestinationRepository } from './repositories/destination.repository';
import { DestinationMediaRepository } from './repositories/destination-media.repository';
import { MediaService } from '../../common/media/media.service';
import { CreateDestinationDto, UpdateDestinationDto } from './dto';
import { DestinationEntity } from '../../entities/destination.entity';
import { serviceMessages } from '../../common/constants/messages';
import { deriveUniqueShortCode } from '../../common/utils/short-code';

const MSG = serviceMessages('Destination');

@Injectable()
export class DestinationService {
  constructor(
    private readonly destRepo: DestinationRepository,
    private readonly mediaRepo: DestinationMediaRepository,
    private readonly media: MediaService,
  ) {}

  async findAll(query?: { page?: number; limit?: number; search?: string }) {
    return this.destRepo.findPaginated({
      page: query?.page,
      limit: query?.limit,
      search: query?.search,
      searchColumns: ['name', 'short_code', 'country', 'city'],
      relations: ['currency'],
      order: { name: 'ASC' },
    });
  }

  async findAllForLookup() {
    return this.destRepo.find({
      select: {
        id: true,
        name: true,
        short_code: true,
        country: true,
        city: true,
        services: true,
        currency_id: true,
      },
      relations: {
        currency: true,
      },
      order: { name: 'ASC' },
    });
  }

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

  async create(dto: CreateDestinationDto) {
    const media = await this.media.prepare(this.mediaRepo, 'destinations', dto.media);
    // short_code is auto-derived from the name and tenant-unique.
    // Manual short_code is no longer accepted from the UI (DTO field
    // dropped) but we still tolerate a value here as a back-compat
    // courtesy for seeders / Excel importers that pass one.
    const shortCode =
      dto.short_code ||
      (await deriveUniqueShortCode(dto.name, 'DEST', (code) =>
        this.destRepo.findByShortCode(code).then(Boolean),
      ));
    const dest = await this.destRepo.save({
      name: dto.name,
      short_code: shortCode,
      country: dto.country,
      city: dto.city,
      currency_id: dto.currency_id || null,
      timezone: dto.timezone || null,
      description: dto.description || null,
      image_url: dto.image_url || null,
      visa_required: dto.visa_required || false,
      tax_types: dto.tax_types || null,
      services: dto.services || null,
    });
    await this.media.sync(this.mediaRepo, dest.id, media);

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

  async update(id: string, dto: UpdateDestinationDto) {
    const dest = await this.destRepo.findById(id);
    if (!dest) throw new NotFoundException(MSG.NOT_FOUND);
    const media = await this.media.prepare(this.mediaRepo, 'destinations', dto.media, id);

    // short_code is immutable post-create — ignore any value on the
    // update payload. Old records that still carry hand-typed codes
    // keep them; the system never overwrites.
    const updateData: Partial<DestinationEntity> = {};
    if (dto.name !== undefined) updateData.name = dto.name;
    if (dto.country !== undefined) updateData.country = dto.country;
    if (dto.city !== undefined) updateData.city = dto.city;
    if (dto.currency_id !== undefined) updateData.currency_id = dto.currency_id;
    if (dto.timezone !== undefined) updateData.timezone = dto.timezone;
    if (dto.description !== undefined) updateData.description = dto.description;
    if (dto.image_url !== undefined) updateData.image_url = dto.image_url;
    if (dto.visa_required !== undefined) updateData.visa_required = dto.visa_required;
    if (dto.tax_types !== undefined) updateData.tax_types = dto.tax_types;
    if (dto.services !== undefined) updateData.services = dto.services;
    // Exchange rate update — stamp `rate_updated_at` so the UI can
    // flag staleness. Only stamps on an actual value change so
    // unrelated edits don't reset the freshness indicator.
    if (dto.usd_to_base_rate !== undefined && dto.usd_to_base_rate !== dest.usd_to_base_rate) {
      updateData.usd_to_base_rate = dto.usd_to_base_rate;
      updateData.rate_updated_at = new Date();
    }

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

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

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