import { getFirestore, Timestamp, FieldValue } from 'firebase-admin/firestore';

// ─────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────

export interface RateLimitResult {
  allowed: boolean;
  retryAfter?: number; // 초 단위
  limit?: 'minute' | 'hour' | 'day'; // 어떤 제한에 걸렸는지
}

type WindowKey = 'minute' | 'hour' | 'day';

interface RateLimitDoc {
  count: number;
  windowStart: Timestamp;
  identifier: string;
  window: WindowKey;
}

// ─────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────

const LIMITS: Record<WindowKey, { max: number; seconds: number }> = {
  minute: { max: 10,  seconds: 60     },
  hour:   { max: 100, seconds: 3600   },
  day:    { max: 500, seconds: 86400  },
} as const;

// CL-7: 과거 버전 조회 전용 rate limit (일반보다 엄격)
const ARCHIVE_LIMITS: Record<WindowKey, { max: number; seconds: number }> = {
  minute: { max: 5,   seconds: 60     },
  hour:   { max: 30,  seconds: 3600   },
  day:    { max: 100, seconds: 86400  },
} as const;

const RATE_LIMITS_COLLECTION = 'rate_limits';

// ─────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────

function docId(identifier: string, window: WindowKey): string {
  return `${identifier}_${window}`;
}

/**
 * 단일 identifier에 대해 주어진 윈도우를 트랜잭션으로 체크하고 카운트를 증가시킵니다.
 * 초과 시 { allowed: false, retryAfter, limit } 를 반환합니다.
 *
 * @param identifier  uid 또는 ip 문자열
 * @param window      체크할 시간 윈도우
 * @param limits      적용할 한도 상수 (기본: LIMITS)
 * @param docIdPrefix docId 접두사 (기본: 없음, archive 전용: 'archive_')
 */
async function checkAndIncrement(
  identifier: string,
  window: WindowKey,
  limits: Record<WindowKey, { max: number; seconds: number }> = LIMITS,
  docIdPrefix = '',
): Promise<RateLimitResult> {
  const db = getFirestore();
  const { max, seconds } = limits[window];
  const id = docIdPrefix ? `${docIdPrefix}${identifier}_${window}` : docId(identifier, window);
  const ref = db.collection(RATE_LIMITS_COLLECTION).doc(id);

  const result = await db.runTransaction<RateLimitResult>(async (tx) => {
    const snap = await tx.get(ref);
    const now = Timestamp.now();

    if (!snap.exists) {
      // 최초 요청 → 문서 생성, 카운트 1
      tx.set(ref, {
        count: 1,
        windowStart: now,
        identifier,
        window,
      } satisfies RateLimitDoc);
      return { allowed: true };
    }

    const data = snap.data() as RateLimitDoc;
    const windowStartSec = data.windowStart.seconds;
    const nowSec = now.seconds;
    const elapsed = nowSec - windowStartSec;

    if (elapsed >= seconds) {
      // 윈도우 만료 → 리셋 후 카운트 1
      tx.set(ref, {
        count: 1,
        windowStart: now,
        identifier,
        window,
      } satisfies RateLimitDoc);
      return { allowed: true };
    }

    if (data.count >= max) {
      // 한도 초과
      const retryAfter = seconds - elapsed;
      return { allowed: false, retryAfter, limit: window };
    }

    // 카운트 증가
    tx.update(ref, { count: FieldValue.increment(1) });
    return { allowed: true };
  });

  return result;
}

// ─────────────────────────────────────────────
// Public API
// ─────────────────────────────────────────────

/**
 * uid 와 ip 각각에 대해 minute / hour / day 윈도우를 모두 체크합니다.
 *
 * - 하나라도 한도를 초과하면 즉시 { allowed: false, retryAfter, limit } 반환
 * - 모두 통과하면 { allowed: true } 반환
 *
 * @param uid  인증된 사용자 UID
 * @param ip   클라이언트 IP 주소
 */
export async function checkRateLimit(uid: string, ip: string): Promise<RateLimitResult> {
  const identifiers: string[] = [uid, ip];
  const windows: WindowKey[] = ['minute', 'hour', 'day'];

  for (const identifier of identifiers) {
    for (const window of windows) {
      const result = await checkAndIncrement(identifier, window);
      if (!result.allowed) {
        return result;
      }
    }
  }

  return { allowed: true };
}

/**
 * CL-7: 과거 버전 조회 전용 rate limit 체크.
 * ARCHIVE_LIMITS(minute:5, hour:30, day:100)를 적용하며
 * docId prefix 'archive_'를 사용하여 일반 rate limit과 분리됩니다.
 *
 * - 하나라도 한도를 초과하면 즉시 { allowed: false, retryAfter, limit } 반환
 * - 모두 통과하면 { allowed: true } 반환
 *
 * @param uid  인증된 사용자 UID
 * @param ip   클라이언트 IP 주소
 */
export async function checkArchiveRateLimit(uid: string, ip: string): Promise<RateLimitResult> {
  const identifiers: string[] = [uid, ip];
  const windows: WindowKey[] = ['minute', 'hour', 'day'];

  for (const identifier of identifiers) {
    for (const window of windows) {
      const result = await checkAndIncrement(identifier, window, ARCHIVE_LIMITS, 'archive_');
      if (!result.allowed) {
        return result;
      }
    }
  }

  return { allowed: true };
}

/**
 * 관리자용: 특정 identifier 의 모든 윈도우 rate limit 문서를 삭제합니다.
 *
 * @param identifier  uid 또는 ip 문자열
 */
export async function resetRateLimit(identifier: string): Promise<void> {
  const db = getFirestore();
  const windows: WindowKey[] = ['minute', 'hour', 'day'];

  const batch = db.batch();
  for (const window of windows) {
    const ref = db.collection(RATE_LIMITS_COLLECTION).doc(docId(identifier, window));
    batch.delete(ref);
  }

  await batch.commit();
}
