import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AvailabilityService, TimeSlot, AvailableDay } from '../core/availability.service';

const DATES_TTL = 300_000; // 5 minutes
const SLOTS_TTL = 120_000; // 2 minutes
const MAX_ENTRIES = 5_000;

interface CacheEntry<T> {
  data: T;
  expiresAt: number;
}

@Injectable()
export class AvailabilityCacheService {
  private cache = new Map<string, CacheEntry<unknown>>();

  constructor(private readonly availability: AvailabilityService) {}

  async getAvailableDates(params: {
    clubId: string;
    serviceId: string;
    staffId: string;
    from: Date;
    to: Date;
  }): Promise<AvailableDay[]> {
    const key = `dates:${params.clubId}:${params.serviceId}:${params.staffId}:${dateStr(params.from)}:${dateStr(params.to)}`;

    const cached = this.getCache<AvailableDay[]>(key);
    if (cached) return cached;

    const result = await this.availability.getAvailableDates(params);
    this.setCache(key, result, DATES_TTL);
    return result;
  }

  async getAvailableSlots(params: {
    clubId: string;
    serviceId: string;
    staffId: string;
    date: Date;
    excludeRecordId?: string;
  }): Promise<TimeSlot[]> {
    if (params.excludeRecordId) {
      return this.availability.getAvailableSlots(params);
    }

    const key = `slots:${params.clubId}:${params.serviceId}:${params.staffId}:${dateStr(params.date)}`;

    const cached = this.getCache<TimeSlot[]>(key);
    if (cached) return cached;

    const result = await this.availability.getAvailableSlots(params);
    this.setCache(key, result, SLOTS_TTL);
    return result;
  }

  /** Invalidate cache when a booking changes */
  @OnEvent('booking.record.*')
  onBookingChange(payload: { clubId?: string; staffId?: string }) {
    if (!payload.clubId || !payload.staffId) return;
    this.invalidateStaff(payload.clubId, payload.staffId);
  }

  invalidateStaff(clubId: string, staffId: string): void {
    const suffix1 = `:${clubId}:`;
    const suffix2 = `:${staffId}:`;
    for (const key of this.cache.keys()) {
      if (key.includes(suffix1) && key.includes(suffix2)) {
        this.cache.delete(key);
      }
    }
  }

  invalidateClub(clubId: string): void {
    const suffix = `:${clubId}:`;
    for (const key of this.cache.keys()) {
      if (key.includes(suffix)) {
        this.cache.delete(key);
      }
    }
  }

  // ─── Internal ──────────────────────────────────────────────

  private getCache<T>(key: string): T | null {
    const entry = this.cache.get(key);
    if (!entry) return null;
    if (Date.now() > entry.expiresAt) {
      this.cache.delete(key);
      return null;
    }
    return entry.data as T;
  }

  private setCache(key: string, data: unknown, ttl: number): void {
    if (this.cache.size >= MAX_ENTRIES) {
      this.evict();
    }
    this.cache.set(key, { data, expiresAt: Date.now() + ttl });
  }

  private evict(): void {
    const now = Date.now();
    for (const [key, entry] of this.cache) {
      if (now > entry.expiresAt) {
        this.cache.delete(key);
      }
    }
    // If still over limit, remove oldest quarter
    if (this.cache.size >= MAX_ENTRIES) {
      const toRemove = Math.ceil(MAX_ENTRIES / 4);
      let removed = 0;
      for (const key of this.cache.keys()) {
        if (removed >= toRemove) break;
        this.cache.delete(key);
        removed++;
      }
    }
  }
}

function dateStr(d: Date): string {
  return d.toISOString().slice(0, 10);
}
