<?php

namespace App\Services\Meta;

use App\Models\AnalyticScript;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class MetaConversionsApiService
{
    private const ALLOWED_EVENTS = [
        'PageView',
        'ViewContent',
        'AddToCart',
        'InitiateCheckout',
        'Purchase',
    ];

    public function track(Request $request, array $payload): array
    {
        $eventName = (string) Arr::get($payload, 'event_name');

        if (! in_array($eventName, self::ALLOWED_EVENTS, true)) {
            return [
                'sent' => false,
                'reason' => 'unsupported_event',
            ];
        }

        $eventId = (string) (Arr::get($payload, 'event_id') ?: $this->makeEventId($eventName));
        $event = [
            'event_name' => $eventName,
            'event_time' => (int) (Arr::get($payload, 'event_time') ?: time()),
            'event_id' => $eventId,
            'action_source' => 'website',
            'event_source_url' => $this->eventSourceUrl($request, $payload),
            'user_data' => $this->userData($request, $payload),
        ];

        $customData = $this->cleanArray((array) Arr::get($payload, 'custom_data', []));
        if ($customData !== []) {
            $event['custom_data'] = $customData;
        }

        return $this->send($event);
    }

    public function resolveClientIp(Request $request): ?string
    {
        foreach (['CF-Connecting-IP', 'True-Client-IP'] as $header) {
            $ip = $this->normalizeIp((string) $request->headers->get($header, ''));
            if ($ip && $this->isValidIp($ip)) {
                return $ip;
            }
        }

        foreach (explode(',', (string) $request->headers->get('X-Forwarded-For', '')) as $candidate) {
            $ip = $this->normalizeIp($candidate);
            if ($ip && $this->isPublicIp($ip)) {
                return $ip;
            }
        }

        $ip = $this->normalizeIp((string) $request->ip());

        return $ip && $this->isValidIp($ip) ? $ip : null;
    }

    private function userData(Request $request, array $payload): array
    {
        $inputUserData = (array) Arr::get($payload, 'user_data', []);
        $userData = [
            'client_ip_address' => $this->resolveClientIp($request),
            'client_user_agent' => $request->userAgent(),
            'fbp' => Arr::get($inputUserData, 'fbp') ?: Arr::get($payload, 'fbp') ?: $request->cookie('_fbp'),
            'fbc' => Arr::get($inputUserData, 'fbc') ?: Arr::get($payload, 'fbc') ?: $request->cookie('_fbc'),
            'external_id' => $this->hashValue(
                Arr::get($inputUserData, 'external_id') ?: Arr::get($inputUserData, 'externalId')
            ),
            'em' => $this->hashValue(
                Arr::get($inputUserData, 'em') ?: Arr::get($inputUserData, 'email'),
                'email'
            ),
            'ph' => $this->hashValue(
                Arr::get($inputUserData, 'ph') ?: Arr::get($inputUserData, 'phone'),
                'phone'
            ),
        ];

        return $this->cleanArray($userData);
    }

    private function send(array $event): array
    {
        $pixelId = $this->pixelId();
        $accessToken = trim((string) (env('META_CAPI_ACCESS_TOKEN') ?: env('META_CONVERSIONS_API_ACCESS_TOKEN')));

        if ($pixelId === null || $accessToken === '') {
            Log::debug('Meta CAPI event skipped because pixel ID or access token is missing.', [
                'event_name' => $event['event_name'] ?? null,
                'event_id' => $event['event_id'] ?? null,
                'has_pixel_id' => $pixelId !== null,
                'has_access_token' => $accessToken !== '',
            ]);

            return [
                'sent' => false,
                'reason' => 'missing_meta_capi_config',
                'event_id' => $event['event_id'] ?? null,
            ];
        }

        $graphVersion = trim((string) env('META_GRAPH_VERSION', 'v20.0')) ?: 'v20.0';
        $graphVersion = str_starts_with($graphVersion, 'v') ? $graphVersion : 'v' . $graphVersion;
        $body = [
            'data' => [$event],
            'access_token' => $accessToken,
        ];

        $testEventCode = trim((string) env('META_TEST_EVENT_CODE', ''));
        if ($testEventCode !== '') {
            $body['test_event_code'] = $testEventCode;
        }

        try {
            $response = Http::asJson()
                ->timeout((int) env('META_CAPI_TIMEOUT', 3))
                ->post("https://graph.facebook.com/{$graphVersion}/{$pixelId}/events", $body);

            if (! $response->successful()) {
                Log::warning('Meta CAPI event was rejected by Meta.', [
                    'event_name' => $event['event_name'] ?? null,
                    'event_id' => $event['event_id'] ?? null,
                    'status' => $response->status(),
                    'response' => $response->json(),
                ]);
            }

            return [
                'sent' => $response->successful(),
                'event_id' => $event['event_id'] ?? null,
                'status' => $response->status(),
                'meta_response' => $response->json(),
            ];
        } catch (\Throwable $exception) {
            Log::warning('Meta CAPI event could not be sent.', [
                'event_name' => $event['event_name'] ?? null,
                'event_id' => $event['event_id'] ?? null,
                'message' => $exception->getMessage(),
            ]);

            return [
                'sent' => false,
                'reason' => 'meta_capi_request_failed',
                'event_id' => $event['event_id'] ?? null,
            ];
        }
    }

    private function pixelId(): ?string
    {
        $pixelId = trim((string) env('META_PIXEL_ID', ''));
        if ($pixelId !== '') {
            return $pixelId;
        }

        if (! Schema::hasTable((new AnalyticScript())->getTable())) {
            return null;
        }

        $scriptId = AnalyticScript::where('type', 'meta_pixel')
            ->where('is_active', 1)
            ->value('script_id');

        $scriptId = trim((string) $scriptId);

        return $scriptId !== '' ? $scriptId : null;
    }

    private function eventSourceUrl(Request $request, array $payload): string
    {
        $sourceUrl = trim((string) Arr::get($payload, 'event_source_url', ''));

        return $sourceUrl !== '' ? $sourceUrl : $request->fullUrl();
    }

    private function hashValue(mixed $value, string $type = 'default'): ?string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }

        if (preg_match('/^[a-f0-9]{64}$/i', $value) === 1) {
            return strtolower($value);
        }

        if ($type === 'email') {
            $value = strtolower($value);
        } elseif ($type === 'phone') {
            $value = preg_replace('/\D+/', '', $value) ?? '';
        } else {
            $value = strtolower($value);
        }

        return $value !== '' ? hash('sha256', $value) : null;
    }

    private function normalizeIp(string $ip): ?string
    {
        $ip = trim($ip, " \t\n\r\0\x0B\"'");
        if ($ip === '') {
            return null;
        }

        if (str_starts_with($ip, '[') && str_contains($ip, ']')) {
            $ip = substr($ip, 1, strpos($ip, ']') - 1);
        } elseif (substr_count($ip, ':') === 1 && preg_match('/^(.+):(\d+)$/', $ip, $matches)) {
            $ip = $matches[1];
        }

        return $ip;
    }

    private function isValidIp(string $ip): bool
    {
        return filter_var($ip, FILTER_VALIDATE_IP) !== false;
    }

    private function isPublicIp(string $ip): bool
    {
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) !== false;
    }

    private function makeEventId(string $eventName): string
    {
        return strtolower($eventName) . '.' . time() . '.' . bin2hex(random_bytes(6));
    }

    private function cleanArray(array $data): array
    {
        $clean = [];

        foreach ($data as $key => $value) {
            if (is_array($value)) {
                $value = $this->cleanArray($value);
            }

            if ($value === null || $value === '' || $value === []) {
                continue;
            }

            $clean[$key] = $value;
        }

        return $clean;
    }
}
