import fs from 'node:fs';
import path from 'node:path';

interface DailyQuotaEntry {
  day: string; // YYYY-MM-DD
  count: number;
}

export class QuotaManager {
  private stateFilePath: string;
  // app_id -> array of millisecond timestamps within sliding minute window
  private minuteWindows = new Map<string, number[]>();
  // app_id -> daily count record
  private dailyCounts = new Map<string, DailyQuotaEntry>();
  private persistTimer: NodeJS.Timeout | null = null;
  private dirty = false;

  constructor(stateDir = './data/state') {
    const resolvedStateDir = path.resolve(stateDir);
    if (!fs.existsSync(resolvedStateDir)) {
      fs.mkdirSync(resolvedStateDir, { recursive: true });
    }
    this.stateFilePath = path.join(resolvedStateDir, 'quota.json');
    this.loadState();

    // Persist dirty state every 10 seconds
    this.persistTimer = setInterval(() => {
      if (this.dirty) {
        this.saveState();
      }
    }, 10000);
    if (this.persistTimer.unref) {
      this.persistTimer.unref();
    }
  }

  private getTodayUtc(): string {
    return new Date().toISOString().slice(0, 10);
  }

  private loadState(): void {
    if (!fs.existsSync(this.stateFilePath)) return;

    try {
      const raw = fs.readFileSync(this.stateFilePath, 'utf8');
      const data = JSON.parse(raw);
      const today = this.getTodayUtc();

      for (const [appId, val] of Object.entries(data)) {
        const entry = val as DailyQuotaEntry;
        if (entry.day === today) {
          this.dailyCounts.set(appId, entry);
        } else {
          // New day, reset
          this.dailyCounts.set(appId, { day: today, count: 0 });
        }
      }
    } catch {
      // If corrupted, fallback to clean state
    }
  }

  public saveState(): void {
    const obj: Record<string, DailyQuotaEntry> = {};
    for (const [appId, entry] of this.dailyCounts.entries()) {
      obj[appId] = entry;
    }

    const tmpPath = `${this.stateFilePath}.tmp.${Date.now()}`;
    try {
      fs.writeFileSync(tmpPath, JSON.stringify(obj, null, 2), { encoding: 'utf8', mode: 0o600 });
      fs.renameSync(tmpPath, this.stateFilePath);
      this.dirty = false;
    } catch {
      try {
        if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
      } catch {}
    }
  }

  /**
   * Check whether the application exceeds its per-minute rate limit.
   * If allowed, records the usage.
   */
  public checkMinuteLimit(appId: string, limit: number): boolean {
    if (limit <= 0) return true; // Unlimited

    const now = Date.now();
    const windowStart = now - 60000;

    let timestamps = this.minuteWindows.get(appId);
    if (!timestamps) {
      timestamps = [];
      this.minuteWindows.set(appId, timestamps);
    }

    // Prune entries older than 60 seconds
    while (timestamps.length > 0 && (timestamps[0] ?? 0) <= windowStart) {
      timestamps.shift();
    }

    if (timestamps.length >= limit) {
      return false; // Rate limited
    }

    timestamps.push(now);
    return true;
  }

  /**
   * Check whether the application exceeds its daily quota.
   * If allowed, increments the counter.
   */
  public checkAndIncrementDailyLimit(appId: string, limit: number): boolean {
    if (limit <= 0) return true; // Unlimited

    const today = this.getTodayUtc();
    let entry = this.dailyCounts.get(appId);

    if (!entry || entry.day !== today) {
      entry = { day: today, count: 0 };
      this.dailyCounts.set(appId, entry);
    }

    if (entry.count >= limit) {
      return false; // Exceeded
    }

    entry.count++;
    this.dirty = true;
    return true;
  }

  public getUsage(appId: string): { currentMinute: number; currentDay: number } {
    const now = Date.now();
    const windowStart = now - 60000;
    const timestamps = this.minuteWindows.get(appId) || [];
    const recent = timestamps.filter((t) => t > windowStart).length;

    const today = this.getTodayUtc();
    const dailyEntry = this.dailyCounts.get(appId);
    const dayCount = (dailyEntry && dailyEntry.day === today) ? dailyEntry.count : 0;

    return {
      currentMinute: recent,
      currentDay: dayCount,
    };
  }

  public stop(): void {
    if (this.persistTimer) {
      clearInterval(this.persistTimer);
      this.persistTimer = null;
    }
    if (this.dirty) {
      this.saveState();
    }
  }
}

