import { Injectable } from '@nestjs/common';
import {
  CODE_TTL_SEC,
  MAX_VERIFY_ATTEMPTS,
  SMS_MIN_INTERVAL_SEC,
  SMS_PER_IP_MAX,
  SMS_PER_IP_WINDOW_SEC,
  SMS_PER_PHONE_MAX,
  SMS_PER_PHONE_WINDOW_SEC,
  VERIFY_BLOCK_DURATION_SEC,
} from './verification.constants';

interface StoredCode {
  code: string;
  createdAt: number;
}


interface FailedAttempts {
  count: number;
  blockedUntil: number;
}

@Injectable()
export class VerificationStoreService {
  private readonly codes = new Map<string, StoredCode>();
  private readonly lastSentAt = new Map<string, number>();
  private readonly sentByPhone: { at: number; phone: string }[] = [];
  private readonly sentByIp: { at: number; ip: string }[] = [];
  private readonly failedAttempts = new Map<string, FailedAttempts>();
  private cleanupInterval: ReturnType<typeof setInterval> | null = null;

  constructor() {
    this.startCleanup();
  }

  private startCleanup() {
    if (this.cleanupInterval) return;
    this.cleanupInterval = setInterval(() => this.cleanup(), 60_000);
  }

  private cleanup() {
    const now = Date.now();
    const borderPhone = now - SMS_PER_PHONE_WINDOW_SEC * 1000;
    const borderIp = now - SMS_PER_IP_WINDOW_SEC * 1000;
    while (this.sentByPhone.length && this.sentByPhone[0].at < borderPhone) this.sentByPhone.shift();
    while (this.sentByIp.length && this.sentByIp[0].at < borderIp) this.sentByIp.shift();

    for (const [phone, data] of this.codes.entries()) {
      if (data.createdAt + CODE_TTL_SEC * 1000 < now) this.codes.delete(phone);
    }

    for (const [key, data] of this.failedAttempts.entries()) {
      if (data.blockedUntil < now) this.failedAttempts.delete(key);
    }
  }

  /** Можно ли отправить SMS на номер (60 сек с последней отправки). */
  canSendToPhone(phone: string): boolean {
    const last = this.lastSentAt.get(phone);
    if (!last) return true;
    return Date.now() - last >= SMS_MIN_INTERVAL_SEC * 1000;
  }

  /** Количество отправок на номер за последние 15 мин. */
  getSendCountByPhone(phone: string): number {
    const border = Date.now() - SMS_PER_PHONE_WINDOW_SEC * 1000;
    return this.sentByPhone.filter((r) => r.at >= border && r.phone === phone).length;
  }

  /** Количество отправок с IP за последние 15 мин. */
  getSendCountByIp(ip: string): number {
    const border = Date.now() - SMS_PER_IP_WINDOW_SEC * 1000;
    return this.sentByIp.filter((r) => r.at >= border && r.ip === ip).length;
  }

  /** Зарегистрировать отправку (инвалидировать старый код, записать лимиты). */
  recordSend(phone: string, ip: string, code: string): void {
    const now = Date.now();
    this.codes.set(phone, { code, createdAt: now });
    this.lastSentAt.set(phone, now);
    this.sentByPhone.push({ at: now, phone });
    this.sentByIp.push({ at: now, ip });
    this.cleanup();
  }

  /** Результат проверки кода. */
  checkCode(
    phone: string,
    ip: string,
    code: string,
  ): { ok: true } | { ok: false; reason: 'expired' } | { ok: false; reason: 'wrong'; remainingAttempts: number } {
    const key = `${phone}:${ip}`;
    const blocked = this.failedAttempts.get(key);
    if (blocked && blocked.blockedUntil > Date.now()) return { ok: false, reason: 'expired' };

    const stored = this.codes.get(phone);
    if (!stored) return { ok: false, reason: 'expired' };
    if (stored.createdAt + CODE_TTL_SEC * 1000 < Date.now()) {
      this.codes.delete(phone);
      return { ok: false, reason: 'expired' };
    }
    if (stored.code !== code) {
      const current = this.failedAttempts.get(key) ?? { count: 0, blockedUntil: 0 };
      current.count += 1;
      if (current.count >= MAX_VERIFY_ATTEMPTS) {
        current.blockedUntil = Date.now() + VERIFY_BLOCK_DURATION_SEC * 1000;
      }
      this.failedAttempts.set(key, current);
      const remainingAttempts = Math.max(0, MAX_VERIFY_ATTEMPTS - current.count);
      return { ok: false, reason: 'wrong', remainingAttempts };
    }
    this.codes.delete(phone);
    this.failedAttempts.delete(key);
    return { ok: true };
  }

  /** Заблокирован ли ввод кода по phone+ip. */
  isVerifyBlocked(phone: string, ip: string): boolean {
    const key = `${phone}:${ip}`;
    const data = this.failedAttempts.get(key);
    if (!data) return false;
    return data.blockedUntil > Date.now();
  }

  /** Оставшееся время блокировки в секундах (0 если не заблокирован). */
  getBlockedRemainingSec(phone: string, ip: string): number {
    const key = `${phone}:${ip}`;
    const data = this.failedAttempts.get(key);
    if (!data || data.blockedUntil <= Date.now()) return 0;
    return Math.ceil((data.blockedUntil - Date.now()) / 1000);
  }
}
