import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { redis } from '../config/redis';
import { spamRateLimits } from '../services/spamshield/spamshield.config';

const REDIS_PREFIX = 'spamshield:ratelimit';

class RedisRateLimiter {
  async checkLimit(
    key: string,
    windowSeconds: number,
    maxRequests: number
  ): Promise<{
    remaining: number;
    resetTime: number;
    retryAfter?: number;
  }> {
    const redisKey = `${REDIS_PREFIX}:${key}`;
    const now = Date.now();

    const current = await redis.get(redisKey);
    const windowStart = now - (now % (windowSeconds * 1000));
    const resetTime = windowStart + windowSeconds * 1000;

    if (!current) {
      const expirySeconds = Math.ceil((resetTime - now) / 1000);
      await redis.set(redisKey, '1', 'EX', expirySeconds);

      return {
        remaining: maxRequests - 1,
        resetTime,
      };
    }

    const count = parseInt(current, 10) + 1;
    await redis.set(redisKey, String(count), 'EX', Math.ceil((resetTime - now) / 1000));

    const remaining = maxRequests - count;

    if (count > maxRequests) {
      return {
        remaining: 0,
        resetTime,
        retryAfter: resetTime - now,
      };
    }

    return {
      remaining,
      resetTime,
    };
  }

  async checkDailyLimit(
    key: string,
    maxPerDay: number
  ): Promise<{
    remaining: number;
    retryAfter?: number;
  }> {
    const redisKey = `${REDIS_PREFIX}:daily:${key}`;
    const now = Date.now();
    const dayStart = new Date(now);
    dayStart.setHours(0, 0, 0, 0);
    const dayEnd = new Date(dayStart);
    dayEnd.setDate(dayEnd.getDate() + 1);
    const resetTime = dayEnd.getTime();

    const current = await redis.get(redisKey);
    const expirySeconds = Math.ceil((resetTime - now) / 1000);

    if (!current) {
      await redis.set(redisKey, '1', 'EX', expirySeconds);

      return {
        remaining: maxPerDay - 1,
      };
    }

    const count = parseInt(current, 10) + 1;
    await redis.set(redisKey, String(count), 'EX', expirySeconds);

    const remaining = maxPerDay - count;

    if (count > maxPerDay) {
      return {
        remaining: 0,
        retryAfter: resetTime - now,
      };
    }

    return {
      remaining,
    };
  }

  reset(key: string) {
    const redisKey = `${REDIS_PREFIX}:${key}`;
    return redis.del(redisKey);
  }
}

export const spamRateLimiter = new RedisRateLimiter();

export async function spamRateLimitMiddleware(fastify: FastifyInstance) {
  fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyReply) => {
    const url = request.url || '';

    if (!url.startsWith('/spamshield')) {
      return;
    }

    const clientIp = request.ip || (request.headers['x-forwarded-for'] as string) || 'unknown';
    const apiKey = request.headers['x-api-key'] as string | undefined;
    const key = apiKey ? `api:${apiKey}` : `ip:${clientIp}`;

    let tier = 'basic';
    if (apiKey) {
      if (apiKey.startsWith('premium_')) {
        tier = 'premium';
      } else if (apiKey.startsWith('plus_')) {
        tier = 'plus';
      }
    }

    const config = spamRateLimits[tier as keyof typeof spamRateLimits];

    const minuteResult = await spamRateLimiter.checkLimit(
      key,
      60,
      config.analysesPerMinute
    );

    const dailyResult = await spamRateLimiter.checkDailyLimit(
      key,
      config.analysesPerDay
    );

    reply.header('X-RateLimit-Limit', config.analysesPerMinute);
    reply.header('X-RateLimit-Remaining', minuteResult.remaining);
    reply.header('X-RateLimit-Reset', Math.ceil(minuteResult.resetTime / 1000));
    reply.header('X-RateLimit-Daily-Limit', config.analysesPerDay);
    reply.header('X-RateLimit-Daily-Remaining', dailyResult.remaining);

    const retryAfter = minuteResult.retryAfter || dailyResult.retryAfter;

    if (retryAfter) {
      reply.header('Retry-After', Math.ceil(retryAfter / 1000));
      reply.code(429);

      return {
        error: 'Too Many Requests',
        message: `Spam analysis rate limit exceeded. Try again in ${Math.ceil(retryAfter / 1000)}s`,
        tier,
        limit: config.analysesPerMinute,
        dailyLimit: config.analysesPerDay,
        reset: new Date(minuteResult.resetTime).toISOString(),
      };
    }

    (request as any).spamRateLimitTier = tier;
  });
}

export { RedisRateLimiter };
