import {
  Injectable,
  ConflictException,
  NotFoundException,
} from '@nestjs/common';
import { ActivityRepository } from './repositories/activity.repository';
import { ActivityTicketRepository } from './repositories/activity-ticket.repository';
import { CreateActivityDto, UpdateActivityDto } from './dto';
import { serviceMessages } from '../../common/constants/messages';

const MSG = serviceMessages('Activity');
const DEFAULT_AGE_CONFIG = [
  { label: 'Child', min_age: 0, max_age: 12 },
  { label: 'Adult', min_age: 13, max_age: 59 },
  { label: 'Senior', min_age: 60, max_age: 99 },
];

@Injectable()
export class ActivityService {
  constructor(
    private readonly repo: ActivityRepository,
    private readonly ticketRepo: ActivityTicketRepository,
  ) {}

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

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

  async findById(id: string) {
    const item = await this.repo.findByIdWithRelations(id);
    if (!item) throw new NotFoundException(MSG.NOT_FOUND);
    // Filter out soft-deleted tickets
    if (item.tickets) {
      item.tickets = item.tickets.filter((t) => !t.is_deleted);
    }
    return item;
  }

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

    if (dto.short_code) {
      const codeExists = await this.repo.findByShortCode(dto.short_code);
      if (codeExists) throw new ConflictException(`Code "${dto.short_code}" already exists`);
    }

    const item = await this.repo.save({
      destination_id: dto.destination_id,
      short_code: dto.short_code || null,
      category: dto.category || null,
      name: dto.name,
      address: dto.address,
      location: dto.location || null,
      cutoff_type: dto.cutoff_type || null,
      cutoff_value: dto.cutoff_value || null,
      operating_days: dto.operating_days || null,
      maintenance_periods: dto.maintenance_periods || null,
      gps_latitude: dto.gps_latitude || null,
      gps_longitude: dto.gps_longitude || null,
      contact_person: dto.contact_person,
      contact_phone: dto.contact_phone || null,
      description: dto.description || null,
      special_instructions: dto.special_instructions || null,
      meeting_point: dto.meeting_point || null,
      age_config: dto.age_config || DEFAULT_AGE_CONFIG,
      cover_image: dto.cover_image || null,
      gallery: dto.gallery || null,
      is_active: dto.is_active ?? true,
    } as any);

    // Create tickets if provided
    if (dto.tickets?.length) {
      for (const ticket of dto.tickets) {
        await this.ticketRepo.save({
          activity_id: item.id,
          name: ticket.name,
          internal_ref: ticket.internal_ref || null,
          time_slot: ticket.time_slot || null,
          duration_mins: ticket.duration_mins ?? null,
          description: ticket.description || null,
          is_active: ticket.is_active ?? true,
        });
      }
    }

    // Auto-create activity pricing for each enabled age group + ticket
    try {
      const tenantId = (this.repo as any).getTenantId();
      const userId = (this.repo as any).cls?.get('user_id') || null;
      const manager = (this.repo as any).repo.manager;

      const dest = await manager.findOne('destinations', { where: { id: dto.destination_id } });
      const currencyId = dest?.currency_id;

      if (currencyId) {
        const ageConfig: any[] = dto.age_config || DEFAULT_AGE_CONFIG;
        const AGE_MAP: Record<string, string> = { Child: 'child', Adult: 'adult', Senior: 'senior' };
        const enabledGroups = ageConfig
          .filter((ag: any) => ag.enabled !== false && (ag.min_age > 0 || ag.max_age > 0))
          .map((ag: any) => AGE_MAP[ag.label] || ag.label.toLowerCase());

        const tickets = dto.tickets?.length ? dto.tickets : [null];
        // Get ticket IDs from the saved tickets
        const savedTickets = await this.ticketRepo.findByActivityId(item.id);

        for (const ticket of (savedTickets.length > 0 ? savedTickets : [null])) {
          for (const ageGroup of enabledGroups) {
            await manager.save('activity_pricing', {
              tenant_id: tenantId,
              destination_id: dto.destination_id,
              activity_id: item.id,
              ticket_id: ticket?.id || null,
              age_group: ageGroup,
              currency_id: currencyId,
              created_by: userId,
              updated_by: userId,
            });
          }
        }
      }
    } catch { /* Don't fail activity creation if pricing auto-create fails */ }

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

  async update(id: string, dto: UpdateActivityDto) {
    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'));
      }
    }

    if (dto.short_code && dto.short_code !== (item as any).short_code) {
      const codeExists = await this.repo.findByShortCode(dto.short_code);
      if (codeExists && codeExists.id !== id) throw new ConflictException(`Code "${dto.short_code}" already exists`);
    }

    const updateData: any = {};
    const fields = [
      'destination_id', 'short_code', 'category', 'name', 'address', 'location',
      'cutoff_type', 'cutoff_value', 'operating_days', 'maintenance_periods',
      'gps_latitude', 'gps_longitude',
      'contact_person', 'contact_phone', 'description',
      'special_instructions', 'meeting_point', 'age_config',
      'cover_image', 'gallery', 'is_active',
    ];
    for (const field of fields) {
      if ((dto as any)[field] !== undefined) updateData[field] = (dto as any)[field];
    }

    await this.repo.update(id, updateData);

    // Handle tickets if provided
    if ((dto as any).tickets !== undefined) {
      const incomingTickets = (dto as any).tickets || [];
      const existingTickets = await this.ticketRepo.findByActivityId(id);

      // Upsert incoming tickets
      const incomingIds = new Set<string>();
      for (const ticket of incomingTickets) {
        if (ticket.id) {
          // Update existing ticket — pick only valid fields
          incomingIds.add(ticket.id);
          await this.ticketRepo.update(ticket.id, {
            name: ticket.name,
            internal_ref: ticket.internal_ref || null,
            time_slot: ticket.time_slot || null,
            duration_mins: ticket.duration_mins ?? null,
            description: ticket.description || null,
            is_active: ticket.is_active ?? true,
          });
        } else {
          // Create new ticket
          const saved = await this.ticketRepo.save({
            activity_id: id,
            name: ticket.name,
            internal_ref: ticket.internal_ref || null,
            time_slot: ticket.time_slot || null,
            duration_mins: ticket.duration_mins ?? null,
            description: ticket.description || null,
            is_active: ticket.is_active ?? true,
          });
          incomingIds.add(saved.id);
        }
      }

      // Soft-delete tickets not in incoming list
      for (const existing of existingTickets) {
        if (!incomingIds.has(existing.id)) {
          await this.ticketRepo.softDelete(existing.id);
        }
      }
    }

    return { id, message: MSG.UPDATED };
  }

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

    // Soft-delete all tickets first
    const tickets = await this.ticketRepo.findByActivityId(id);
    for (const ticket of tickets) {
      await this.ticketRepo.softDelete(ticket.id);
    }

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