<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Webhook;
use App\Models\WebhookLog;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;

/**
 * Advanced Webhook Service
 * 
 * Provides comprehensive webhook management with retry logic,
 * signature verification, and event filtering.
 */
class AdvancedWebhookService
{
    private const MAX_RETRIES = 3;
    private const RETRY_DELAYS = [30, 300, 1800]; // 30s, 5m, 30m
    private const TIMEOUT = 30;

    /**
     * Send webhook event
     */
    public function sendWebhookEvent(string $eventType, array $payload, ?int $webhookId = null): void
    {
        $webhooks = $webhookId 
            ? Webhook::where('id', $webhookId)->get()
            : Webhook::where('is_active', true)
                ->whereJsonContains('events', $eventType)
                ->get();

        foreach ($webhooks as $webhook) {
            $this->processWebhook($webhook, $eventType, $payload);
        }
    }

    /**
     * Process individual webhook
     */
    private function processWebhook(Webhook $webhook, string $eventType, array $payload): void
    {
        try {
            // Prepare webhook data
            $webhookData = $this->prepareWebhookData($webhook, $eventType, $payload);
            
            // Send webhook
            $response = $this->sendHttpRequest($webhook, $webhookData);
            
            // Log the attempt
            $this->logWebhookAttempt($webhook, $eventType, $webhookData, $response, true);
            
            // Update webhook statistics
            $this->updateWebhookStats($webhook, true);
            
        } catch (\Exception $e) {
            // Log failed attempt
            $this->logWebhookAttempt($webhook, $eventType, $payload, null, false, $e->getMessage());
            
            // Update webhook statistics
            $this->updateWebhookStats($webhook, false);
            
            // Queue for retry if not exceeded max retries
            $this->queueWebhookRetry($webhook, $eventType, $payload);
        }
    }

    /**
     * Prepare webhook data with signature
     */
    private function prepareWebhookData(Webhook $webhook, string $eventType, array $payload): array
    {
        $timestamp = now()->timestamp;
        $nonce = Str::random(16);
        
        $data = [
            'id' => Str::uuid()->toString(),
            'event' => $eventType,
            'timestamp' => $timestamp,
            'nonce' => $nonce,
            'data' => $payload,
            'version' => '1.0',
        ];

        // Add signature if secret is configured
        if ($webhook->secret) {
            $data['signature'] = $this->generateSignature($webhook->secret, $data);
        }

        return $data;
    }

    /**
     * Generate webhook signature
     */
    private function generateSignature(string $secret, array $data): string
    {
        $payload = json_encode($data);
        return 'sha256=' . hash_hmac('sha256', $payload, $secret);
    }

    /**
     * Send HTTP request to webhook URL
     */
    private function sendHttpRequest(Webhook $webhook, array $data): Response
    {
        $headers = [
            'Content-Type' => 'application/json',
            'User-Agent' => 'Sekuret-Webhook/1.0',
            'X-Webhook-Event' => $data['event'],
            'X-Webhook-Timestamp' => (string) $data['timestamp'],
            'X-Webhook-Nonce' => $data['nonce'],
        ];

        if (isset($data['signature'])) {
            $headers['X-Webhook-Signature'] = $data['signature'];
        }

        return Http::timeout(self::TIMEOUT)
            ->withHeaders($headers)
            ->post($webhook->url, $data);
    }

    /**
     * Log webhook attempt
     */
    private function logWebhookAttempt(
        Webhook $webhook,
        string $eventType,
        array $payload,
        ?Response $response,
        bool $success,
        ?string $errorMessage = null
    ): void {
        WebhookLog::create([
            'webhook_id' => $webhook->id,
            'event_type' => $eventType,
            'url' => $webhook->url,
            'payload' => $payload,
            'response_status' => $response?->status(),
            'response_body' => $response?->body(),
            'success' => $success,
            'error_message' => $errorMessage,
            'attempt_number' => $this->getAttemptNumber($webhook, $eventType),
            'execution_time' => $this->getExecutionTime(),
        ]);
    }

    /**
     * Update webhook statistics
     */
    private function updateWebhookStats(Webhook $webhook, bool $success): void
    {
        $webhook->increment('total_attempts');
        
        if ($success) {
            $webhook->increment('successful_attempts');
            $webhook->update(['last_successful_at' => now()]);
        } else {
            $webhook->increment('failed_attempts');
            $webhook->update(['last_failed_at' => now()]);
        }
    }

    /**
     * Queue webhook for retry
     */
    private function queueWebhookRetry(Webhook $webhook, string $eventType, array $payload): void
    {
        $attemptNumber = $this->getAttemptNumber($webhook, $eventType);
        
        if ($attemptNumber < self::MAX_RETRIES) {
            $delay = self::RETRY_DELAYS[$attemptNumber - 1] ?? 1800;
            
            Queue::later(now()->addSeconds($delay), function () use ($webhook, $eventType, $payload) {
                $this->processWebhook($webhook, $eventType, $payload);
            });
        } else {
            // Mark webhook as failed after max retries
            $this->markWebhookAsFailed($webhook, $eventType);
        }
    }

    /**
     * Get attempt number for webhook
     */
    private function getAttemptNumber(Webhook $webhook, string $eventType): int
    {
        return WebhookLog::where('webhook_id', $webhook->id)
            ->where('event_type', $eventType)
            ->where('created_at', '>=', now()->subHour())
            ->count() + 1;
    }

    /**
     * Mark webhook as failed
     */
    private function markWebhookAsFailed(Webhook $webhook, string $eventType): void
    {
        Log::error('Webhook failed after max retries', [
            'webhook_id' => $webhook->id,
            'url' => $webhook->url,
            'event_type' => $eventType,
        ]);

        // Optionally disable webhook after repeated failures
        if ($webhook->failed_attempts > 10) {
            $webhook->update(['is_active' => false]);
        }
    }

    /**
     * Verify webhook signature
     */
    public function verifyWebhookSignature(string $signature, string $payload, string $secret): bool
    {
        $expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secret);
        return hash_equals($expectedSignature, $signature);
    }

    /**
     * Test webhook endpoint
     */
    public function testWebhook(Webhook $webhook): array
    {
        $testPayload = [
            'id' => Str::uuid()->toString(),
            'event' => 'webhook.test',
            'timestamp' => now()->timestamp,
            'data' => [
                'message' => 'This is a test webhook from Sekuret License Management System',
                'test_id' => Str::random(8),
            ],
        ];

        try {
            $response = $this->sendHttpRequest($webhook, $testPayload);
            
            return [
                'success' => $response->successful(),
                'status_code' => $response->status(),
                'response_body' => $response->body(),
                'response_time' => $response->transferStats?->getHandlerStat('total_time') ?? 0,
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage(),
                'status_code' => 0,
                'response_body' => null,
                'response_time' => 0,
            ];
        }
    }

    /**
     * Get webhook statistics
     */
    public function getWebhookStats(Webhook $webhook, int $days = 30): array
    {
        $startDate = now()->subDays($days);
        
        $logs = WebhookLog::where('webhook_id', $webhook->id)
            ->where('created_at', '>=', $startDate)
            ->get();

        $totalAttempts = $logs->count();
        $successfulAttempts = $logs->where('success', true)->count();
        $failedAttempts = $logs->where('success', false)->count();
        
        $successRate = $totalAttempts > 0 ? ($successfulAttempts / $totalAttempts) * 100 : 0;
        
        $avgResponseTime = $logs->where('execution_time', '>', 0)
            ->avg('execution_time');

        $events = $logs->groupBy('event_type')
            ->map(fn($group) => $group->count())
            ->toArray();

        return [
            'total_attempts' => $totalAttempts,
            'successful_attempts' => $successfulAttempts,
            'failed_attempts' => $failedAttempts,
            'success_rate' => round($successRate, 2),
            'average_response_time' => round($avgResponseTime, 3),
            'events_breakdown' => $events,
            'last_successful' => $webhook->last_successful_at,
            'last_failed' => $webhook->last_failed_at,
        ];
    }

    /**
     * Clean up old webhook logs
     */
    public function cleanupOldLogs(int $days = 90): int
    {
        $cutoffDate = now()->subDays($days);
        
        return WebhookLog::where('created_at', '<', $cutoffDate)->delete();
    }

    /**
     * Get webhook health status
     */
    public function getWebhookHealth(Webhook $webhook): array
    {
        $recentLogs = WebhookLog::where('webhook_id', $webhook->id)
            ->where('created_at', '>=', now()->subHours(24))
            ->get();

        $recentFailures = $recentLogs->where('success', false)->count();
        $totalRecent = $recentLogs->count();
        
        $healthScore = $totalRecent > 0 ? (($totalRecent - $recentFailures) / $totalRecent) * 100 : 100;
        
        $status = match (true) {
            $healthScore >= 95 => 'excellent',
            $healthScore >= 80 => 'good',
            $healthScore >= 60 => 'fair',
            default => 'poor',
        };

        return [
            'status' => $status,
            'health_score' => round($healthScore, 1),
            'recent_failures' => $recentFailures,
            'total_recent_attempts' => $totalRecent,
            'last_attempt' => $recentLogs->sortByDesc('created_at')->first()?->created_at,
        ];
    }

    /**
     * Bulk webhook operations
     */
    public function bulkUpdateWebhooks(array $webhookIds, array $updates): int
    {
        return Webhook::whereIn('id', $webhookIds)->update($updates);
    }

    /**
     * Get webhook delivery report
     */
    public function getDeliveryReport(int $days = 7): array
    {
        $startDate = now()->subDays($days);
        
        $webhooks = Webhook::with(['logs' => function ($query) use ($startDate) {
            $query->where('created_at', '>=', $startDate);
        }])->get();

        $report = [];
        foreach ($webhooks as $webhook) {
            $logs = $webhook->logs;
            $totalAttempts = $logs->count();
            $successfulAttempts = $logs->where('success', true)->count();
            
            $report[] = [
                'webhook_id' => $webhook->id,
                'name' => $webhook->name,
                'url' => $webhook->url,
                'total_attempts' => $totalAttempts,
                'successful_attempts' => $successfulAttempts,
                'success_rate' => $totalAttempts > 0 ? round(($successfulAttempts / $totalAttempts) * 100, 2) : 0,
                'last_attempt' => $logs->sortByDesc('created_at')->first()?->created_at,
                'health_status' => $this->getWebhookHealth($webhook)['status'],
            ];
        }

        return $report;
    }

    /**
     * Get execution time for performance tracking
     */
    private function getExecutionTime(): float
    {
        // This would be set at the beginning of the request
        return microtime(true) - ($_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true));
    }

    /**
     * Send webhook event asynchronously
     */
    public function sendWebhookEventAsync(string $eventType, array $payload, ?int $webhookId = null): void
    {
        Queue::push(function () use ($eventType, $payload, $webhookId) {
            $this->sendWebhookEvent($eventType, $payload, $webhookId);
        });
    }

    /**
     * Validate webhook URL
     */
    public function validateWebhookUrl(string $url): array
    {
        $errors = [];
        
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            $errors[] = 'Invalid URL format';
        }
        
        if (!in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https'])) {
            $errors[] = 'URL must use HTTP or HTTPS protocol';
        }
        
        if (parse_url($url, PHP_URL_HOST) === 'localhost' || 
            parse_url($url, PHP_URL_HOST) === '127.0.0.1') {
            $errors[] = 'Localhost URLs are not allowed for webhooks';
        }
        
        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }
}
