import {
  Injectable,
  ConflictException,
  NotFoundException,
  ForbiddenException,
} from '@nestjs/common';
import { FindOptionsOrder, FindOptionsWhere } from 'typeorm';
import { SeasonRepository } from './repositories/season.repository';
import { SeasonEntity } from '../../entities/season.entity';
import { CreateSeasonDto, UpdateSeasonDto } from './dto';

export interface SeasonMatrixEntry {
  id: string;
  start_date: string | null;
  end_date: string | null;
  durations: unknown;
  is_active: boolean;
  sort_order: number;
}
import { serviceMessages } from '../../common/constants/messages';

const MSG = serviceMessages('Season');

@Injectable()
export class SeasonService {
  constructor(private readonly repo: SeasonRepository) {}

  async findAll(
    query?: { page?: number; limit?: number; search?: string },
    filters?: { destinationId?: string },
  ) {
    const where: FindOptionsWhere<SeasonEntity> = {};
    if (filters?.destinationId) where.destination_id = filters.destinationId;

    return this.repo.findPaginated({
      page: query?.page,
      limit: query?.limit,
      search: query?.search,
      searchColumns: ['name'],
      relations: ['destination'],
      order: { sort_order: 'ASC' },
      where,
    });
  }

  async findByDestination(destinationId: string) {
    return this.repo.findByDestination(destinationId);
  }

  async findAllForLookup() {
    return this.repo.find({
      select: {
        id: true,
        name: true,
        destination_id: true,
        start_date: true,
        end_date: true,
      },
      order: { name: 'ASC' } as FindOptionsOrder<SeasonEntity>,
    });
  }

  /**
   * Matrix view: all seasons grouped by destination.
   */
  async findMatrix() {
    const allSeasons = await this.repo.findAllWithDestination();
    const destMap = new Map<
      string,
      {
        destination_id: string;
        destination_name: string;
        seasons: Record<string, SeasonMatrixEntry>;
      }
    >();

    for (const season of allSeasons) {
      const destId = season.destination_id;
      if (!destMap.has(destId)) {
        destMap.set(destId, {
          destination_id: destId,
          destination_name: season.destination?.name || '—',
          seasons: {},
        });
      }
      destMap.get(destId)!.seasons[season.name] = {
        id: season.id,
        start_date: season.start_date,
        end_date: season.end_date,
        durations: season.durations || null,
        is_active: season.is_active,
        sort_order: season.sort_order,
      };
    }

    return Array.from(destMap.values());
  }

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

  async create(dto: CreateSeasonDto) {
    const existing = await this.repo.findByNameAndDestination(dto.name, dto.destination_id);
    if (existing) throw new ConflictException(MSG.ALREADY_EXISTS('name'));

    const item = await this.repo.save({
      destination_id: dto.destination_id,
      name: dto.name,
      start_date: dto.start_date || null,
      end_date: dto.end_date || null,
      durations: dto.durations || null,
      sort_order: dto.sort_order ?? 0,
      is_active: dto.is_active ?? true,
      is_system: false,
    });

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

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

    if (dto.name || dto.destination_id) {
      const checkName = dto.name || item.name;
      const checkDest = dto.destination_id || item.destination_id;
      if (checkName !== item.name || checkDest !== item.destination_id) {
        const existing = await this.repo.findByNameAndDestination(checkName, checkDest);
        if (existing && existing.id !== id) throw new ConflictException(MSG.ALREADY_EXISTS('name'));
      }
    }

    const updateData: Partial<SeasonEntity> = {};
    for (const field of [
      'destination_id',
      'name',
      'start_date',
      'end_date',
      'durations',
      'sort_order',
      'is_active',
    ]) {
      const value = (dto as Record<string, unknown>)[field];
      if (value !== undefined) {
        (updateData as Record<string, unknown>)[field] = value;
      }
    }

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

  async remove(id: string) {
    const item = await this.repo.findById(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);
    if (item.is_system) throw new ForbiddenException('System seasons cannot be deleted');

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