<?php

namespace CaptchaPlugin\Services;

use CaptchaPlugin\Models\CaptchaSetting;
use CaptchaPlugin\Models\CaptchaStatistic;
use CaptchaPlugin\Models\CaptchaRateLimit;
use CaptchaPlugin\Services\Providers\CaptchaProviderInterface;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;

class CaptchaService
{
    protected ?CaptchaProviderInterface $provider = null;
    protected ?CaptchaProviderInterface $fallbackProvider = null;
    protected ?CaptchaSetting $settings = null;

    public function __construct()
    {
        $this->loadSettings();
        $this->initializeProvider();
    }

    /**
     * Load CAPTCHA settings from database
     */
    protected function loadSettings(): void
    {
        $this->settings = Cache::remember('captcha_settings', 3600, function () {
            return CaptchaSetting::first() ?? $this->createDefaultSettings();
        });
    }

    /**
     * Create default settings if none exist
     */
    protected function createDefaultSettings(): CaptchaSetting
    {
        return CaptchaSetting::create([
            'provider' => config('captcha.default', 'hcaptcha'),
            'enabled' => config('captcha.enabled', false),
            'testing_mode' => config('captcha.testing_mode', false),
        ]);
    }

    /**
     * Initialize the CAPTCHA provider
     */
    protected function initializeProvider(): void
    {
        if (!$this->settings || !$this->settings->enabled) {
            return;
        }

        try {
            $providerConfig = $this->getProviderConfig($this->settings->provider);
            $this->provider = CaptchaFactory::make($this->settings->provider, $providerConfig);

            // Initialize fallback if configured
            if ($this->settings->fallback_provider !== 'none' && $this->settings->fallback_site_key) {
                $fallbackConfig = $this->getProviderConfig($this->settings->fallback_provider, true);
                $this->fallbackProvider = CaptchaFactory::make($this->settings->fallback_provider, $fallbackConfig);
            }
        } catch (\Exception $e) {
            Log::error('Failed to initialize CAPTCHA provider: ' . $e->getMessage());
        }
    }

    /**
     * Get provider configuration
     */
    protected function getProviderConfig(string $provider, bool $isFallback = false): array
    {
        $config = config("captcha.providers.{$provider}", []);

        if ($isFallback) {
            $config['site_key'] = $this->settings->fallback_site_key;
            $config['secret_key'] = $this->settings->fallback_secret_key;
        } else {
            $config['site_key'] = $this->settings->site_key ?? $config['site_key'];
            $config['secret_key'] = $this->settings->secret_key ?? $config['secret_key'];
            $config['api_key'] = $this->settings->api_key ?? $config['api_key'] ?? null;

            if ($provider === 'recaptcha_v3') {
                $config['threshold'] = $this->settings->recaptcha_v3_threshold ?? $config['threshold'];
            }
        }

        return $config;
    }

    /**
     * Check if CAPTCHA is enabled
     */
    public function isEnabled(string $location = null): bool
    {
        if (!$this->settings || !$this->settings->enabled || $this->settings->testing_mode) {
            return false;
        }

        if ($location) {
            $field = "enabled_{$location}";
            return $this->settings->$field ?? false;
        }

        return true;
    }

    /**
     * Check if IP is whitelisted
     */
    public function isWhitelisted(?string $ipAddress = null): bool
    {
        $ipAddress = $ipAddress ?? Request::ip();

        $whitelistIps = $this->settings->whitelist_ips ?? config('captcha.whitelist_ips', []);

        return in_array($ipAddress, $whitelistIps);
    }

    /**
     * Verify CAPTCHA response
     */
    public function verify(string $response, string $location = 'unknown', ?string $ipAddress = null): array
    {
        $ipAddress = $ipAddress ?? Request::ip();

        // Check if testing mode
        if ($this->settings && $this->settings->testing_mode) {
            return $this->mockSuccessResponse();
        }

        // Check if whitelisted
        if ($this->isWhitelisted($ipAddress)) {
            return $this->mockSuccessResponse('IP whitelisted');
        }

        // Check rate limiting
        if ($this->isRateLimited($ipAddress, $location)) {
            return [
                'success' => false,
                'error' => 'Too many failed attempts. Please try again later.',
                'rate_limited' => true,
            ];
        }

        // Verify with primary provider
        $result = $this->verifyWithProvider($this->provider, $response, $ipAddress);

        // Try fallback if primary fails
        if (!$result['success'] && $this->fallbackProvider) {
            Log::info('Primary CAPTCHA failed, trying fallback provider');
            $result = $this->verifyWithProvider($this->fallbackProvider, $response, $ipAddress);
            $result['fallback_used'] = true;
        }

        // Record statistics
        $this->recordStatistics($location, $ipAddress, $result);

        // Update rate limiting
        $this->updateRateLimit($ipAddress, $location, $result['success']);

        return $result;
    }

    /**
     * Verify with a specific provider
     */
    protected function verifyWithProvider(?CaptchaProviderInterface $provider, string $response, string $ipAddress): array
    {
        if (!$provider) {
            return [
                'success' => false,
                'error' => 'CAPTCHA provider not configured',
            ];
        }

        return $provider->verify($response, $ipAddress);
    }

    /**
     * Mock success response for testing/whitelist
     */
    protected function mockSuccessResponse(string $reason = 'Testing mode'): array
    {
        return [
            'success' => true,
            'score' => 1.0,
            'error' => null,
            'response_time' => 0,
            'mocked' => true,
            'reason' => $reason,
        ];
    }

    /**
     * Check if IP is rate limited
     */
    protected function isRateLimited(string $ipAddress, string $location): bool
    {
        if (!config('captcha.rate_limit.enabled', true)) {
            return false;
        }

        $rateLimit = CaptchaRateLimit::where('ip_address', $ipAddress)
            ->where('location', $location)
            ->first();

        if (!$rateLimit) {
            return false;
        }

        // Check if blocked
        if ($rateLimit->blocked_until && $rateLimit->blocked_until->isFuture()) {
            return true;
        }

        return false;
    }

    /**
     * Update rate limit for IP
     */
    protected function updateRateLimit(string $ipAddress, string $location, bool $success): void
    {
        if (!config('captcha.rate_limit.enabled', true)) {
            return;
        }

        $rateLimit = CaptchaRateLimit::firstOrCreate(
            ['ip_address' => $ipAddress, 'location' => $location],
            ['first_attempt_at' => now(), 'last_attempt_at' => now()]
        );

        $rateLimit->attempts++;
        $rateLimit->last_attempt_at = now();

        if (!$success) {
            $rateLimit->failed_attempts++;

            $maxAttempts = config('captcha.rate_limit.max_attempts', 5);
            if ($rateLimit->failed_attempts >= $maxAttempts) {
                $blockDuration = config('captcha.rate_limit.block_duration', 60);
                $rateLimit->blocked_until = now()->addMinutes($blockDuration);
            }
        } else {
            // Reset failed attempts on success
            $rateLimit->failed_attempts = 0;
            $rateLimit->blocked_until = null;
        }

        $rateLimit->save();
    }

    /**
     * Record verification statistics
     */
    protected function recordStatistics(string $location, string $ipAddress, array $result): void
    {
        if (!config('captcha.statistics.enabled', true) || !$this->provider) {
            return;
        }

        try {
            CaptchaStatistic::create([
                'provider' => $this->provider->getName(),
                'location' => $location,
                'ip_address' => $ipAddress,
                'user_agent' => Request::userAgent(),
                'success' => $result['success'],
                'score' => $result['score'] ?? null,
                'error_code' => $result['error'] ?? null,
                'error_message' => $result['error'] ?? null,
                'response_time_ms' => $result['response_time'] ?? 0,
                'metadata' => $result['metadata'] ?? null,
                'verified_at' => now(),
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to record CAPTCHA statistics: ' . $e->getMessage());
        }
    }

    /**
     * Render CAPTCHA HTML
     */
    public function render(array $attributes = []): string
    {
        if (!$this->provider || !$this->isEnabled()) {
            return '';
        }

        return $this->provider->render($attributes);
    }

    /**
     * Get script URL
     */
    public function getScriptUrl(): ?string
    {
        return $this->provider?->getScriptUrl();
    }

    /**
     * Get provider name
     */
    public function getProviderName(): ?string
    {
        return $this->provider?->getName();
    }

    /**
     * Get site key
     */
    public function getSiteKey(): ?string
    {
        return $this->provider?->getSiteKey();
    }

    /**
     * Clear cached settings
     */
    public function clearCache(): void
    {
        Cache::forget('captcha_settings');
        $this->loadSettings();
        $this->initializeProvider();
    }

    /**
     * Get statistics summary
     */
    public function getStatistics(int $days = 7): array
    {
        $stats = CaptchaStatistic::where('created_at', '>=', now()->subDays($days))
            ->selectRaw('
                provider,
                location,
                COUNT(*) as total_attempts,
                SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
                SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed,
                AVG(response_time_ms) as avg_response_time,
                AVG(CASE WHEN score IS NOT NULL THEN score ELSE NULL END) as avg_score
            ')
            ->groupBy('provider', 'location')
            ->get();

        return $stats->toArray();
    }
}
