<?php

namespace PloverCRM\Modules\Automations\Actions;

use PloverCRM\Modules\Integrations\Services\AutomationsLogger;
use PloverCRM\Modules\Integrations\Services\GoogleAdsConnectionService;
use PloverCRM\Modules\Integrations\Services\GoogleTokenService;

/**
 * Sends a Google Ads offline conversion for an automation run.
 */
class SendGoogleAdsConversionAction
{
    private GoogleTokenService $tokenService;
    private AutomationsLogger $logger;

    public function __construct()
    {
        $this->tokenService = new GoogleTokenService();
        $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 Google Ads connection was provided.');
        }

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

        $googleConfig = get_option('plover_crm_google_config', []);
        $developerToken = (string) ($googleConfig['developer_token'] ?? '');

        if ($developerToken === '') {
            throw new \RuntimeException('The Google Ads developer token is not configured.');
        }

        $accessToken = $this->tokenService->getValidAccessToken();
        $conversionData = $this->buildConversionPayload($contact, $config);
        $apiResult = $this->sendToGoogleAdsApi(
            $conversionData,
            $connection,
            $accessToken,
            $developerToken,
            (int) ($contact['id'] ?? 0)
        );

        return [
            'type' => 'send_google_ads_conversion',
            'connection_id' => $connectionId,
            'conversion_action_id' => $conversionData['conversionActionId'],
            'conversion_action_name' => (string) ($config['conversion_action_name'] ?? ''),
            'stage' => (string) ($payload['context']['stage']['to'] ?? ''),
            'response_code' => $apiResult['response_code'],
        ];
    }

    private function buildConversionPayload(array $contact, array $config): array
    {
        $conversionActionId = trim((string) ($config['conversion_action_id'] ?? ''));
        if ($conversionActionId === '') {
            throw new \RuntimeException('The Google Ads conversion action ID is missing.');
        }

        $customFields = $this->normalizeCustomFieldsMap($contact);
        $userIdentifiers = $this->buildUserIdentifiers($contact);
        $clickIdentifiers = $this->resolveClickIdentifiers($customFields);
        $gclid = $clickIdentifiers['gclid'];
        $gbraid = $clickIdentifiers['gbraid'];
        $wbraid = $clickIdentifiers['wbraid'];

        if (empty($userIdentifiers) && !$gclid && !$gbraid && !$wbraid) {
            throw new \RuntimeException('No Google Ads user identifiers or click identifiers were found for this contact.');
        }

        $conversionData = [
            'conversionActionId' => $conversionActionId,
            'conversionDateTime' => gmdate('Y-m-d H:i:sP'),
            'conversionValue' => isset($config['conversion_value']) && $config['conversion_value'] !== ''
                ? (float) $config['conversion_value']
                : 1.0,
            'currencyCode' => strtoupper((string) ($config['currency_code'] ?? 'USD')),
            'conversionEnvironment' => $this->resolveConversionEnvironment($gbraid, $wbraid),
            'userIdentifiers' => $userIdentifiers,
        ];

        if ($gclid) {
            $conversionData['gclid'] = $gclid;
        }

        if ($gbraid) {
            $conversionData['gbraid'] = $gbraid;
        }

        if ($wbraid) {
            $conversionData['wbraid'] = $wbraid;
        }

        return $conversionData;
    }

    private function buildUserIdentifiers(array $contact): array
    {
        $identifiers = [];

        if (!empty($contact['email'])) {
            $identifiers[] = [
                'hashedEmail' => hash('sha256', $this->normalizeEmail((string) $contact['email'])),
                'userIdentifierSource' => 'FIRST_PARTY',
            ];
        }

        if (!empty($contact['phone'])) {
            $normalizedPhone = $this->normalizePhone((string) $contact['phone']);
            if ($normalizedPhone) {
                $identifiers[] = [
                    'hashedPhoneNumber' => hash('sha256', $normalizedPhone),
                    'userIdentifierSource' => 'FIRST_PARTY',
                ];
            }
        }

        return $identifiers;
    }

    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 getClickIdentifier(array $customFields, string $key): ?string
    {
        $value = trim((string) ($customFields[$key] ?? ''));
        return $value !== '' ? $value : null;
    }

    private function resolveClickIdentifiers(array $customFields): array
    {
        $gclid = $this->getClickIdentifier($customFields, 'gclid');
        $gbraid = $this->getClickIdentifier($customFields, 'gbraid');
        $wbraid = $this->getClickIdentifier($customFields, 'wbraid');

        if ($gclid) {
            return [
                'gclid' => $gclid,
                'gbraid' => null,
                'wbraid' => null,
            ];
        }

        if ($gbraid) {
            return [
                'gclid' => null,
                'gbraid' => $gbraid,
                'wbraid' => null,
            ];
        }

        return [
            'gclid' => null,
            'gbraid' => null,
            'wbraid' => $wbraid,
        ];
    }

    private function resolveConversionEnvironment(?string $gbraid, ?string $wbraid): string
    {
        if ($gbraid) {
            return 'APP';
        }

        if ($wbraid) {
            return 'WEB';
        }

        return 'WEB';
    }

    private function normalizeEmail(string $email): string
    {
        $email = strtolower(trim($email));

        if (preg_match('/^(.+)@(gmail\.com|googlemail\.com)$/i', $email, $matches)) {
            $username = str_replace('.', '', $matches[1]);

            if (strpos($username, '+') !== false) {
                $username = substr($username, 0, strpos($username, '+'));
            }

            return $username . '@' . strtolower($matches[2]);
        }

        return $email;
    }

    private function normalizePhone(string $phone): ?string
    {
        $hasPlus = substr($phone, 0, 1) === '+';
        $digits = preg_replace('/[^0-9]/', '', $phone);

        if ($digits === '' || !$hasPlus) {
            return null;
        }

        $normalized = '+' . $digits;

        if (!preg_match('/^\+[1-9]\d{7,14}$/', $normalized)) {
            return null;
        }

        return $normalized;
    }

    private function sendToGoogleAdsApi(array $conversionData, $connection, string $accessToken, string $developerToken, int $contactId): array
    {
        $customerId = $connection->customer_id;
        $url = "https://googleads.googleapis.com/v23/customers/{$customerId}:uploadClickConversions";
        $conversionActionResourceName = "customers/{$customerId}/conversionActions/{$conversionData['conversionActionId']}";

        $conversion = [
            'conversionAction' => $conversionActionResourceName,
            'conversionDateTime' => $conversionData['conversionDateTime'],
            'conversionValue' => $conversionData['conversionValue'],
            'currencyCode' => $conversionData['currencyCode'],
            'conversionEnvironment' => $conversionData['conversionEnvironment'] ?? 'WEB',
            'consent' => [
                'adUserData' => 'GRANTED',
            ],
        ];

        foreach (['gclid', 'gbraid', 'wbraid'] as $clickKey) {
            if (!empty($conversionData[$clickKey])) {
                $conversion[$clickKey] = $conversionData[$clickKey];
            }
        }

        if (!empty($conversionData['userIdentifiers'])) {
            $conversion['userIdentifiers'] = $conversionData['userIdentifiers'];
        }

        $headers = [
            'Authorization' => 'Bearer ' . $accessToken,
            'developer-token' => $developerToken,
            'Content-Type' => 'application/json',
        ];

        if (!empty($connection->manager_id)) {
            $headers['login-customer-id'] = $connection->manager_id;
        }

        $requestBody = [
            'conversions' => [$conversion],
            'partialFailure' => true,
            'validateOnly' => false,
        ];

        $start = microtime(true);
        $response = wp_remote_post($url, [
            'headers' => $headers,
            'body' => wp_json_encode($requestBody),
            'timeout' => 30,
            'httpversion' => '1.1',
            'sslverify' => true,
        ]);
        $executionTime = round((microtime(true) - $start) * 1000, 2);

        if (is_wp_error($response)) {
            $message = $response->get_error_message();
            $this->logger->logGoogleConversionSent(
                $conversionData['conversionActionId'],
                $customerId,
                $contactId,
                wp_json_encode($requestBody),
                $executionTime,
                0,
                'WP_Error: ' . $message
            );
            throw new \RuntimeException($message);
        }

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

        $this->logger->logGoogleConversionSent(
            $conversionData['conversionActionId'],
            $customerId,
            $contactId,
            wp_json_encode($requestBody),
            $executionTime,
            $responseCode,
            $responseBody
        );

        if ($responseCode !== 200) {
            throw new \RuntimeException('Google Ads API request failed with HTTP ' . $responseCode . '.');
        }

        $partialFailureMessage = $this->extractPartialFailureMessage($decodedBody);
        if ($partialFailureMessage !== null) {
            throw new \RuntimeException($partialFailureMessage);
        }

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

    private function extractPartialFailureMessage(?array $responseBody): ?string
    {
        if (empty($responseBody['partialFailureError']) || !is_array($responseBody['partialFailureError'])) {
            return null;
        }

        $partialFailure = $responseBody['partialFailureError'];
        $message = trim((string) ($partialFailure['message'] ?? ''));

        if ($message !== '') {
            return $message;
        }

        if (!empty($partialFailure['details']) && is_array($partialFailure['details'])) {
            foreach ($partialFailure['details'] as $detail) {
                if (!is_array($detail)) {
                    continue;
                }

                $detailMessage = trim((string) ($detail['message'] ?? ''));
                if ($detailMessage !== '') {
                    return $detailMessage;
                }
            }
        }

        return 'Google Ads reported a partial failure while uploading the conversion.';
    }
}
