<?php

namespace App\Filters;

use App\Services\Support\TenantContext;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Database;

/**
 * Configured per-route as 'ratelimit:auth' or 'ratelimit:search' etc,
 * mapping to a named limit in Config\RateLimits. Falls back to a DB-backed
 * counter (rate_limit_hits table) when Redis is not configured — Redis
 * stays genuinely optional per the project requirements, not a soft
 * dependency the app breaks without.
 */
class RateLimit implements FilterInterface
{
    public function before(RequestInterface $request, $arguments = null)
    {
        $limitName = $arguments[0] ?? 'default';
        $limits = [
            'default' => (int) (env('ratelimit.default') ?? 60),
            'auth'    => (int) (env('ratelimit.auth') ?? 10),
            'search'  => (int) (env('ratelimit.search') ?? 30),
        ];
        $maxRequests = $limits[$limitName] ?? $limits['default'];
        $windowSeconds = 60;

        $identifier = $this->resolveIdentifier($request);
        $route = $request->getUri()->getPath();

        $currentCount = $this->redisAvailable()
            ? $this->checkRedis($identifier, $route, $windowSeconds)
            : $this->checkDatabase($identifier, $route, $windowSeconds);

        if ($currentCount > $maxRequests) {
            return service('response')
                ->setStatusCode(429)
                ->setHeader('Retry-After', (string) $windowSeconds)
                ->setJSON([
                    'success' => false,
                    'data'    => null,
                    'errors'  => [['code' => 'RATE_LIMITED', 'message' => 'Too many requests. Please slow down.']],
                ]);
        }
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        // no-op
    }

    private function resolveIdentifier(RequestInterface $request): string
    {
        $user = TenantContext::getInstance()->getUser();
        return $user ? "user:{$user->id}" : "ip:{$request->getIPAddress()}";
    }

    /**
     * Redis stays genuinely optional (per the SaaS requirements): only
     * attempted when the Predis client class is actually present (it
     * won't be on a Composer-less shared-hosting deploy — see
     * 10-deployment-guide.md) AND redis.enabled=true in .env. Any other
     * state falls back to the DB-backed counter below, no configuration
     * required.
     */
    private function redisAvailable(): bool
    {
        return (env('redis.enabled') === 'true' || env('redis.enabled') === true)
            && class_exists('Predis\Client');
    }

    private function checkRedis(string $identifier, string $route, int $windowSeconds): int
    {
        $client = new \Predis\Client([
            'host'     => env('redis.host') ?? '127.0.0.1',
            'port'     => (int) (env('redis.port') ?? 6379),
            'password' => env('redis.password') ?: null,
        ]);

        $key = "ratelimit:{$identifier}:{$route}";
        $count = $client->incr($key);
        if ($count === 1) {
            $client->expire($key, $windowSeconds);
        }
        return $count;
    }

    private function checkDatabase(string $identifier, string $route, int $windowSeconds): int
    {
        $db = Database::connect();
        $windowStart = date('Y-m-d H:i:s', time() - $windowSeconds);

        $row = $db->table('rate_limit_hits')
            ->where('identifier', $identifier)
            ->where('route', $route)
            ->where('window_start >=', $windowStart)
            ->get()->getRow();

        if ($row) {
            $db->table('rate_limit_hits')->where('id', $row->id)->update(['hit_count' => $row->hit_count + 1]);
            return $row->hit_count + 1;
        }

        $db->table('rate_limit_hits')->insert([
            'identifier'   => $identifier,
            'route'        => $route,
            'hit_count'    => 1,
            'window_start' => date('Y-m-d H:i:s'),
        ]);

        return 1;
    }
}
