<?php

namespace PloverCRM\Modules\Automations\Actions;

use PloverCRM\Core\Services\PlaceholderService;
use PloverCRM\Core\Services\SystemFieldRegistry;
use PloverCRM\Modules\Integrations\Services\AutomationsLogger;
use PloverCRM\Modules\Integrations\Services\MetaCapiConnectionService;
use FluentCrm\App\Hooks\Handlers\CountryNames;

/**
 * Sends a Meta CAPI event for an automation run.
 */
class SendMetaCapiEventAction
{
    private AutomationsLogger $logger;

    public function __construct()
    {
        $this->logger = new AutomationsLogger();
    }

    public function execute(array $config, array $payload): array
    {
        $contact = $payload['contact'] ?? [];
        $connectionId = absint($config['connection_id'] ?? 0);

        if ($connectionId <= 0) {
            throw new \RuntimeException('No Meta CAPI connection was provided.');
        }

        $connection = MetaCapiConnectionService::getConnection($connectionId);
        if (!$connection || !$connection->isValid()) {
            throw new \RuntimeException('The selected Meta CAPI connection is invalid.');
        }

        $eventName = trim((string) ($config['event_name'] ?? ''));
        if ($eventName === 'Custom') {
            $eventName = trim((string) ($config['custom_event_name'] ?? ''));
        }

        if ($eventName === '') {
            throw new \RuntimeException('The Meta event name is missing.');
        }

        $requestPayload = $this->buildEventPayload($contact, $config, $eventName);
        $apiResult = $this->sendEvent(
            $requestPayload,
            $connection->dataset_id,
            $connection->access_token,
            (int) ($contact['id'] ?? 0),
            (string) ($payload['context']['stage']['to'] ?? '')
        );

        return [
            'type' => 'send_meta_capi_event',
            'connection_id' => $connectionId,
            'event_name' => $eventName,
            'stage' => (string) ($payload['context']['stage']['to'] ?? ''),
            'response_code' => $apiResult['response_code'],
        ];
    }

    private function buildEventPayload(array $contact, array $config, string $eventName): array
    {
        $event = [
            'event_name' => $eventName,
            'event_time' => time(),
            'action_source' => 'system_generated',
            'user_data' => $this->buildUserData($contact),
        ];

        $customData = $this->buildCustomData($contact, $config);
        if (!empty($customData)) {
            $event['custom_data'] = $customData;
        }

        return [
            'data' => [$event],
        ];
    }

    private function buildUserData(array $contact): array
    {
        $customFields = $this->normalizeCustomFieldsMap($contact);
        $userData = [
            'external_id' => (string) ($contact['id'] ?? ''),
        ];

        $normalizedFirstName = $this->normalizePersonName($contact['first_name'] ?? '');
        if ($normalizedFirstName !== null) {
            $userData['fn'] = $this->hashNormalizedValue($normalizedFirstName);
        }

        $normalizedLastName = $this->normalizePersonName($contact['last_name'] ?? '');
        if ($normalizedLastName !== null) {
            $userData['ln'] = $this->hashNormalizedValue($normalizedLastName);
        }

        $normalizedEmail = $this->normalizeEmail($contact['email'] ?? '');
        if ($normalizedEmail !== null) {
            $userData['em'] = $this->hashNormalizedValue($normalizedEmail);
        }

        $normalizedPhone = $this->normalizePhone($contact['phone'] ?? '');
        if ($normalizedPhone !== null) {
            $userData['ph'] = $this->hashNormalizedValue($normalizedPhone);
        }

        $normalizedDateOfBirth = $this->normalizeDateOfBirth($contact['date_of_birth'] ?? '');
        if ($normalizedDateOfBirth !== null) {
            $userData['db'] = $this->hashNormalizedValue($normalizedDateOfBirth);
        }

        $normalizedGender = $this->normalizeGender($customFields['gender'] ?? '');
        if ($normalizedGender !== null) {
            $userData['ge'] = $this->hashNormalizedValue($normalizedGender);
        }

        $normalizedCountry = $this->normalizeCountryCode($contact['country'] ?? '');
        if ($normalizedCountry !== null) {
            $userData['country'] = $this->hashNormalizedValue($normalizedCountry);
        }

        $normalizedPostalCode = $this->normalizePostalCode($contact['postal_code'] ?? '', $normalizedCountry);
        if ($normalizedPostalCode !== null) {
            $userData['zp'] = $this->hashNormalizedValue($normalizedPostalCode);
        }

        $normalizedCity = $this->normalizeCity($contact['city'] ?? '');
        if ($normalizedCity !== null) {
            $userData['ct'] = $this->hashNormalizedValue($normalizedCity);
        }

        $normalizedState = $this->normalizeState($contact['state'] ?? '', $normalizedCountry);
        if ($normalizedState !== null) {
            $userData['st'] = $this->hashNormalizedValue($normalizedState);
        }

        if (!empty($contact['ip'])) {
            $userData['client_ip_address'] = (string) $contact['ip'];
        }

        $facebookLeadId = $customFields[SystemFieldRegistry::FIELD_FACEBOOK_LEAD_ID] ?? null;
        if (!empty($facebookLeadId)) {
            $userData['lead_id'] = (int) $facebookLeadId;
        }

        return $userData;
    }

    private function hashNormalizedValue(string $value): string
    {
        return hash('sha256', $value);
    }

    private function normalizeEmail(string $value): ?string
    {
        $normalized = strtolower(trim($value));

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

    private function normalizePhone(string $value): ?string
    {
        $digits = preg_replace('/\D+/', '', $value);
        $digits = ltrim((string) $digits, '0');

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

    private function normalizePersonName(string $value): ?string
    {
        $normalized = $this->normalizeUtf8Text($value);
        $normalized = preg_replace('/[^\p{L}\p{N}\s]/u', '', $normalized);
        $normalized = preg_replace('/\s+/u', ' ', (string) $normalized);
        $normalized = trim((string) $normalized);

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

    private function normalizeDateOfBirth(string $value): ?string
    {
        $normalized = preg_replace('/\D+/', '', $value);

        return strlen((string) $normalized) === 8 ? (string) $normalized : null;
    }

    private function normalizeGender(string $value): ?string
    {
        $normalized = strtolower(trim($value));

        if ($normalized === '') {
            return null;
        }

        return substr($normalized, 0, 1);
    }

    private function normalizeCity(string $value): ?string
    {
        return $this->normalizeCompactGeographicValue($value);
    }

    private function normalizeState(string $value, ?string $countryCode): ?string
    {
        $normalized = $this->normalizeUtf8Text($value);

        if ($normalized === '') {
            return null;
        }

        if ($countryCode === 'us') {
            $stateCode = $this->resolveUsStateCode($normalized);
            if ($stateCode !== null) {
                return $stateCode;
            }
        }

        return $this->normalizeCompactGeographicValue($normalized);
    }

    private function normalizePostalCode(string $value, ?string $countryCode): ?string
    {
        $normalized = strtolower(trim($value));
        $normalized = preg_replace('/[\s-]+/u', '', $normalized);
        $normalized = trim((string) $normalized);

        if ($normalized === '') {
            return null;
        }

        if ($countryCode === 'us' && preg_match('/^\d{5,}$/', $normalized)) {
            return substr($normalized, 0, 5);
        }

        return $normalized;
    }

    private function normalizeCountryCode(string $value): ?string
    {
        $normalized = trim($value);

        if ($normalized === '') {
            return null;
        }

        if (preg_match('/\(([A-Za-z]{2})\)\s*$/', $normalized, $matches)) {
            return strtolower($matches[1]);
        }

        $normalized = strtoupper($normalized);

        if (preg_match('/^[A-Z]{2}$/', $normalized)) {
            return strtolower($normalized);
        }

        static $countryCodesByName = null;

        if ($countryCodesByName === null) {
            $countryCodesByName = [];
            foreach ((new CountryNames())->get() as $country) {
                $title = isset($country['title']) ? strtolower(trim((string) $country['title'])) : '';
                $code = isset($country['code']) ? strtolower(trim((string) $country['code'])) : '';

                if ($title !== '' && $code !== '') {
                    $countryCodesByName[$title] = $code;
                    $strippedTitle = strtolower(trim(preg_replace('/\s*\([^)]+\)\s*$/', '', $title)));
                    if ($strippedTitle !== '') {
                        $countryCodesByName[$strippedTitle] = $code;
                    }
                }
            }
        }

        $lookup = strtolower(trim(preg_replace('/\s*\([^)]+\)\s*$/', '', $value)));

        $aliases = [
            'usa' => 'us',
            'u.s.a' => 'us',
            'u.s.' => 'us',
            'united states of america' => 'us',
            'uk' => 'gb',
            'u.k.' => 'gb',
        ];

        if (isset($aliases[$lookup])) {
            return $aliases[$lookup];
        }

        return $countryCodesByName[$lookup] ?? null;
    }

    private function normalizeCompactGeographicValue(string $value): ?string
    {
        $normalized = $this->normalizeUtf8Text($value);
        $normalized = preg_replace('/[^\p{L}\p{N}]/u', '', $normalized);
        $normalized = trim((string) $normalized);

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

    private function normalizeUtf8Text(string $value): string
    {
        $normalized = function_exists('mb_strtolower')
            ? mb_strtolower(trim($value), 'UTF-8')
            : strtolower(trim($value));

        return $normalized;
    }

    private function resolveUsStateCode(string $value): ?string
    {
        static $stateCodes = [
            'alabama' => 'al',
            'alaska' => 'ak',
            'arizona' => 'az',
            'arkansas' => 'ar',
            'california' => 'ca',
            'colorado' => 'co',
            'connecticut' => 'ct',
            'delaware' => 'de',
            'district of columbia' => 'dc',
            'florida' => 'fl',
            'georgia' => 'ga',
            'hawaii' => 'hi',
            'idaho' => 'id',
            'illinois' => 'il',
            'indiana' => 'in',
            'iowa' => 'ia',
            'kansas' => 'ks',
            'kentucky' => 'ky',
            'louisiana' => 'la',
            'maine' => 'me',
            'maryland' => 'md',
            'massachusetts' => 'ma',
            'michigan' => 'mi',
            'minnesota' => 'mn',
            'mississippi' => 'ms',
            'missouri' => 'mo',
            'montana' => 'mt',
            'nebraska' => 'ne',
            'nevada' => 'nv',
            'new hampshire' => 'nh',
            'new jersey' => 'nj',
            'new mexico' => 'nm',
            'new york' => 'ny',
            'north carolina' => 'nc',
            'north dakota' => 'nd',
            'ohio' => 'oh',
            'oklahoma' => 'ok',
            'oregon' => 'or',
            'pennsylvania' => 'pa',
            'rhode island' => 'ri',
            'south carolina' => 'sc',
            'south dakota' => 'sd',
            'tennessee' => 'tn',
            'texas' => 'tx',
            'utah' => 'ut',
            'vermont' => 'vt',
            'virginia' => 'va',
            'washington' => 'wa',
            'west virginia' => 'wv',
            'wisconsin' => 'wi',
            'wyoming' => 'wy',
        ];

        $normalized = strtolower(trim($value));

        if ($normalized === '') {
            return null;
        }

        if (preg_match('/^[a-z]{2}$/', $normalized)) {
            return $normalized;
        }

        return $stateCodes[$normalized] ?? null;
    }

    private function buildCustomData(array $contact, array $config): array
    {
        $customData = [
            'event_source' => 'crm',
            'lead_event_source' => 'PloverCRM',
        ];

        if (!empty($config['custom_data']) && is_array($config['custom_data'])) {
            foreach ($config['custom_data'] as $key => $value) {
                $converted = $this->convertParameterValue($key, PlaceholderService::parse((string) $value, $contact));
                if ($converted !== null && $converted !== '') {
                    $customData[$key] = $converted;
                }
            }
        }

        if (!empty($config['custom_data_parameters']) && is_array($config['custom_data_parameters'])) {
            foreach ($config['custom_data_parameters'] as $param) {
                $paramType = $param['type'] ?? '';
                $paramValue = $param['value'] ?? '';

                if ($paramType === '' || $paramValue === '') {
                    continue;
                }

                $parsedValue = PlaceholderService::parse((string) $paramValue, $contact);

                if ($paramType === 'Custom Parameter') {
                    $parts = explode('|', $parsedValue, 2);
                    if (count($parts) === 2) {
                        $customName = trim($parts[0]);
                        $customValue = trim($parts[1]);
                        if ($customName !== '' && $customValue !== '') {
                            $customData[$customName] = $this->convertParameterValue($customName, $customValue);
                        }
                    }
                    continue;
                }

                $converted = $this->convertParameterValue($paramType, $parsedValue);
                if ($converted !== null && $converted !== '') {
                    $customData[$paramType] = $converted;
                }
            }
        }

        return $customData;
    }

    private function normalizeCustomFieldsMap(array $contact): array
    {
        if (!empty($contact['custom_fields_map']) && is_array($contact['custom_fields_map'])) {
            return $contact['custom_fields_map'];
        }

        $normalized = [];
        foreach ((array) ($contact['custom_fields'] ?? []) as $field) {
            if (is_array($field) && !empty($field['key'])) {
                $normalized[$field['key']] = $field['value'] ?? '';
            }
        }

        return $normalized;
    }

    private function convertParameterValue(string $paramType, $paramValue)
    {
        if (in_array($paramType, ['payment_info_available', 'success'], true)) {
            if (is_bool($paramValue)) {
                return $paramValue;
            }

            return strtolower((string) $paramValue) === 'true' || (string) $paramValue === '1';
        }

        if (in_array($paramType, ['num_items', 'max_rating_value'], true)) {
            return is_numeric($paramValue) ? (int) $paramValue : null;
        }

        if (in_array($paramType, ['predicted_ltv', 'value'], true)) {
            return is_numeric($paramValue) ? (float) $paramValue : null;
        }

        if (in_array($paramType, ['content_ids', 'contents'], true)) {
            if (is_string($paramValue)) {
                $decoded = json_decode($paramValue, true);
                if (is_array($decoded)) {
                    return $decoded;
                }

                return array_map('trim', explode(',', $paramValue));
            }

            return is_array($paramValue) ? $paramValue : [$paramValue];
        }

        if ($paramType === 'currency') {
            return strtoupper((string) $paramValue);
        }

        return $paramValue !== '' ? (string) $paramValue : null;
    }

    private function sendEvent(array $payload, string $datasetId, string $accessToken, int $contactId, string $stage): array
    {
        $apiUrl = add_query_arg(
            ['access_token' => $accessToken],
            "https://graph.facebook.com/v24.0/{$datasetId}/events"
        );

        $args = [
            'method' => 'POST',
            'headers' => [
                'Content-Type' => 'application/json',
                'User-Agent' => 'PloverCRM-Automations/1.0',
            ],
            'body' => wp_json_encode($payload),
            'timeout' => 30,
            'httpversion' => '1.1',
            'sslverify' => true,
            'blocking' => true,
        ];

        $start = microtime(true);
        $response = wp_remote_post($apiUrl, $args);
        $executionTime = round((microtime(true) - $start) * 1000, 2);

        if (is_wp_error($response)) {
            $message = $response->get_error_message();
            $this->logger->logMetaApiError($stage, $message, [
                'subscriber_id' => $contactId,
            ]);
            throw new \RuntimeException($message);
        }

        $responseCode = (int) wp_remote_retrieve_response_code($response);
        $responseBody = (string) wp_remote_retrieve_body($response);
        $decoded = json_decode($responseBody, true);

        if ($responseCode !== 200 || isset($decoded['error'])) {
            $errorMessage = $decoded['error']['message'] ?? ('HTTP Error: ' . $responseCode);
            $this->logger->logMetaApiError($stage, $errorMessage, [
                'subscriber_id' => $contactId,
                'response_code' => $responseCode,
                'response_body' => $responseBody,
            ]);
            throw new \RuntimeException($errorMessage);
        }

        $eventName = $payload['data'][0]['event_name'] ?? 'Unknown';
        $this->logger->logMetaEventSent($stage, $datasetId, [
            'event_name' => $eventName,
            'subscriber_id' => $contactId,
            'request_body' => wp_json_encode($payload),
            'execution_time_ms' => $executionTime,
            'response_code' => $responseCode,
            'response_body' => $responseBody,
        ]);

        return [
            'response_code' => $responseCode,
            'response_body' => $responseBody,
        ];
    }
}
