<?php

namespace App\Http\Middleware;

use App\Models\ApiClient;
use App\Services\Api\ApiAccessAuditService;
use App\Services\Api\ApiSecurityService;
use Closure;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;

class CheckApiClient
{
    private $security;
    private $audit;

    public function __construct(ApiSecurityService $security, ApiAccessAuditService $audit)
    {
        $this->security = $security;
        $this->audit = $audit;
    }

    public function handle($request, Closure $next, string $requiredScope = null)
    {
        $startedAt = microtime(true);

        if ($this->httpsRequired($request)) {
            $this->audit->denied($request, null, 'HTTPS is required for API access.', 403, $requiredScope);
            return $this->deny('HTTPS is required for API access.', 403);
        }

        $clientKey = $request->header('X-Client-Key') ?: $request->input('client_key');
        $clientSecret = $request->header('X-Client-Secret') ?: $request->input('client_secret');

        $authorization = $request->header('Authorization');
        if (! $clientSecret && $authorization && stripos($authorization, 'Bearer ') === 0) {
            $clientSecret = trim(substr($authorization, 7));
        }

        if (! $clientKey) {
            $this->audit->denied($request, null, 'Missing API client key. Provide X-Client-Key header.', 401, $requiredScope);
            return $this->deny('Missing API client key. Provide X-Client-Key header.', 401);
        }

        $client = ApiClient::where('client_key', $clientKey)->where('is_active', 1)->first();
        if (! $client) {
            $this->audit->denied($request, null, 'Invalid API client credentials.', 401, $requiredScope);
            return $this->deny('Invalid API client credentials.', 401);
        }

        if (! $this->ipAllowed($request->ip(), $client->ipList())) {
            $client->forceFill(['failed_attempts' => ((int) $client->failed_attempts) + 1, 'last_failed_at' => now()])->save();
            $this->audit->denied($request, $client, 'API client is not allowed from this IP address.', 403, $requiredScope);
            return $this->deny('API client is not allowed from this IP address.', 403);
        }

        $rateLimit = (int) ($client->rate_limit_per_minute ?: config('ngbvd_api_security.rate_limit_per_minute', 60));
        $rateKey = 'api_client:' . $client->id . ':' . ($requiredScope ?: 'default') . ':' . $request->ip();
        if (RateLimiter::tooManyAttempts($rateKey, $rateLimit)) {
            $retryAfter = RateLimiter::availableIn($rateKey);
            $this->audit->denied($request, $client, 'Too many API requests.', 429, $requiredScope);
            return response()->json([
                'success' => false,
                'message' => 'Too many API requests. Try again later.',
                'retry_after_seconds' => $retryAfter,
            ], 429)->header('Retry-After', $retryAfter)->header('Cache-Control', 'no-store');
        }

        RateLimiter::hit($rateKey, 60);
        $remaining = max(0, $rateLimit - RateLimiter::attempts($rateKey));

        $plainSecret = null;
        if ($clientSecret && Hash::check($clientSecret, $client->client_secret_hash)) {
            $plainSecret = $clientSecret;
        }

        $requiresSignature = (bool) ($client->require_signed_requests ?? config('ngbvd_api_security.require_signed_requests', false));
        $signaturePresent = $request->headers->has('X-Signature') || $request->headers->has('X-Nonce') || $request->headers->has('X-Timestamp');
        $signatureStatus = $signaturePresent ? 'present' : 'not_required';

        if ($requiresSignature || $signaturePresent) {
            if (! $this->security->timestampIsFresh($request, $client)) {
                $client->forceFill(['failed_attempts' => ((int) $client->failed_attempts) + 1, 'last_failed_at' => now()])->save();
                $this->audit->denied($request, $client, 'Invalid or expired API timestamp.', 401, $requiredScope, 'invalid_timestamp');
                return $this->deny('Invalid or expired API timestamp.', 401);
            }

            if (! $this->security->nonceIsNew($request, $client)) {
                $client->forceFill(['failed_attempts' => ((int) $client->failed_attempts) + 1, 'last_failed_at' => now()])->save();
                $this->audit->denied($request, $client, 'Duplicate or invalid API nonce.', 409, $requiredScope, 'replay_detected');
                return $this->deny('Duplicate or invalid API nonce.', 409);
            }

            if (! $this->security->verifySignature($request, $client, $plainSecret)) {
                $client->forceFill(['failed_attempts' => ((int) $client->failed_attempts) + 1, 'last_failed_at' => now()])->save();
                $this->audit->denied($request, $client, 'Invalid API request signature.', 401, $requiredScope, 'invalid_signature');
                return $this->deny('Invalid API request signature.', 401);
            }

            $signatureStatus = 'valid';
        } elseif (! $plainSecret) {
            $client->forceFill(['failed_attempts' => ((int) $client->failed_attempts) + 1, 'last_failed_at' => now()])->save();
            $this->audit->denied($request, $client, 'Invalid API client credentials.', 401, $requiredScope, 'secret_invalid');
            return $this->deny('Invalid API client credentials.', 401);
        }

        if ($requiredScope && ! $client->canUseScope($requiredScope)) {
            $this->audit->denied($request, $client, 'API client is not allowed to use this scope.', 403, $requiredScope, $signatureStatus);
            return $this->deny('API client is not allowed to use this scope.', 403);
        }

        $client->forceFill(['last_used_at' => now(), 'failed_attempts' => 0])->save();
        $request->attributes->set('api_client', $client);

        $accessLog = $this->audit->start($request, $client, $requiredScope, $signatureStatus, $rateLimit, $remaining);

        try {
            $response = $next($request);
            $this->audit->complete($accessLog, $request, $response, $startedAt);

            $response->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
            $response->headers->set('X-Content-Type-Options', 'nosniff');
            $response->headers->set('X-NGBVD-Request-Id', $request->attributes->get('ngbvd_api_request_id'));

            return $response;
        } catch (\Throwable $e) {
            if ($accessLog) {
                $accessLog->fill([
                    'status' => 'failed',
                    'error_message' => $e->getMessage(),
                    'duration_ms' => (int) round((microtime(true) - $startedAt) * 1000),
                    'request_completed_at' => now(),
                    'last_attempt_at' => now(),
                ])->save();
            }

            throw $e;
        }
    }

    private function httpsRequired($request): bool
    {
        $required = (bool) config('ngbvd_api_security.require_https', app()->environment('production'));
        if (! $required) {
            return false;
        }

        return ! $request->isSecure() && strtolower((string) $request->header('X-Forwarded-Proto')) !== 'https';
    }

    private function ipAllowed(string $ip, array $allowed): bool
    {
        if (empty($allowed)) {
            return true;
        }

        foreach ($allowed as $rule) {
            if ($ip === $rule) {
                return true;
            }

            if (strpos($rule, '/') !== false && $this->cidrMatch($ip, $rule)) {
                return true;
            }
        }

        return false;
    }

    private function cidrMatch(string $ip, string $cidr): bool
    {
        [$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
        if ($mask === null || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false || filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) {
            return false;
        }

        $mask = (int) $mask;
        $ipLong = ip2long($ip);
        $subnetLong = ip2long($subnet);
        $maskLong = -1 << (32 - $mask);

        return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
    }

    private function deny(string $message, int $status)
    {
        return response()->json([
            'success' => false,
            'message' => $message,
        ], $status)->header('Cache-Control', 'no-store');
    }
}
