<?php

namespace Daniv04\HaciendaPackage\Http\Controllers;

use Daniv04\HaciendaPackage\Models\Receipt;
use Daniv04\HaciendaPackage\Services\Webhook\WebhookService;
use Daniv04\HaciendaPackage\Services\Webhook\WebhookVerifierService;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;

class HaciendaWebhookController extends BaseController
{
    public function __construct(
        protected WebhookService $webhookService,
        protected WebhookVerifierService $webhookVerifier,
    ) {}

    public function handle(Request $request): JsonResponse
    {
        try {
            $data = $request->all();
            Log::info('Received webhook from Hacienda', ['clave' => $data['clave'] ?? 'missing']);

            // Validate IP whitelist
            $ipValidation = $this->validateIpWhitelist($request);
            if (!$ipValidation['valid']) {
                Log::warning('Webhook rejected: IP not whitelisted', [
                    'ip' => $request->ip(),
                    'reason' => $ipValidation['reason'],
                ]);
                return $this->ErrorResponse('Webhook rejected: ' . $ipValidation['reason'], 403);
            }

            // Validate timestamp
            $timestampValidation = $this->validateTimestamp($data);
            if (!$timestampValidation['valid']) {
                Log::warning('Webhook rejected: Invalid timestamp', [
                    'reason' => $timestampValidation['reason'],
                ]);
                return $this->ErrorResponse('Webhook rejected: ' . $timestampValidation['reason'], 403);
            }

            if (empty($data['clave'])) {
                return $this->ErrorResponse('Missing clave in webhook payload', 400);
            }

            $receipt = Receipt::where('ui_key', $data['clave'])->first();

            if (!$receipt) {
                Log::warning('Webhook received for unknown clave', ['clave' => $data['clave']]);
                return $this->ErrorResponse('Receipt not found', 404);
            }

            // Verify webhook authenticity (clave + XML signature)
            $verification = $this->webhookVerifier->verify($data, $receipt);

            if (!$verification['valid']) {
                Log::warning('Webhook verification failed', [
                    'clave' => $data['clave'],
                    'reason' => $verification['reason'],
                ]);
                return $this->ErrorResponse('Webhook verification failed', 403);
            }

            $this->webhookService->handleHaciendaResponse($receipt, $data);

            Log::info('Webhook processed successfully', [
                'receipt_id' => $receipt->id,
                'ui_key' => $receipt->ui_key,
            ]);

            return response()->json([
                'success' => true,
                'message' => 'Webhook processed successfully',
                'invoice_id' => $receipt->id,
                'ui_key' => $receipt->ui_key,
            ], 200);

        } catch (\Exception $e) {
            Log::error('Error processing webhook', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            return $this->ErrorResponse('Internal server error', 500);
        }
    }

    private function validateIpWhitelist(Request $request): array
    {
        $allowedIps = config('invoicing.webhook_validation.allowed_ips', '');

        // Skip IP validation if no IPs are configured
        if (empty(trim($allowedIps))) {
            return ['valid' => true, 'reason' => null];
        }

        $clientIp = $request->ip();
        $ips = array_map('trim', explode(',', $allowedIps));

        foreach ($ips as $ip) {
            if ($this->ipMatches($clientIp, $ip)) {
                return ['valid' => true, 'reason' => null];
            }
        }

        return [
            'valid' => false,
            'reason' => "IP {$clientIp} is not whitelisted",
        ];
    }

    private function ipMatches(string $clientIp, string $configIp): bool
    {
        // Exact match
        if ($clientIp === $configIp) {
            return true;
        }

        // CIDR notation support (e.g., 192.0.2.0/24)
        if (strpos($configIp, '/') !== false) {
            return $this->ipInCidr($clientIp, $configIp);
        }

        return false;
    }

    private function ipInCidr(string $ip, string $cidr): bool
    {
        [$subnet, $bits] = explode('/', $cidr);
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = -1 << (32 - (int) $bits);
        $subnet &= $mask;
        return ($ip & $mask) === $subnet;
    }

    private function validateTimestamp(array $data): array
    {
        if (empty($data['fecha'])) {
            return ['valid' => false, 'reason' => 'Missing timestamp (fecha)'];
        }

        try {
            // Parse the Hacienda timestamp format (ISO 8601)
            $webhookTime = \DateTime::createFromFormat(
                'Y-m-d\TH:i:s',
                substr($data['fecha'], 0, 19),
                new \DateTimeZone('UTC')
            );

            if (!$webhookTime) {
                return ['valid' => false, 'reason' => 'Invalid timestamp format'];
            }

            $maxAge = config('invoicing.webhook_validation.max_age_seconds', 600);
            $now = new \DateTime('now', new \DateTimeZone('UTC'));
            $ageSeconds = $now->getTimestamp() - $webhookTime->getTimestamp();

            if ($ageSeconds < -30) {
                // Allow 30 seconds of clock skew in the future
                return ['valid' => false, 'reason' => 'Webhook timestamp is in the future'];
            }

            if ($ageSeconds > $maxAge) {
                return [
                    'valid' => false,
                    'reason' => "Webhook timestamp is older than {$maxAge} seconds ({$ageSeconds}s old)",
                ];
            }

            return ['valid' => true, 'reason' => null];

        } catch (\Exception $e) {
            return ['valid' => false, 'reason' => 'Error validating timestamp: ' . $e->getMessage()];
        }
    }
}
