<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Commerce;

use Help4BuilderSuite\Support\IpTools;

if (! defined('ABSPATH')) {
    exit;
}

final class ProcessorRouter
{
    private const REQUEST_TIMEOUT = 20;

    /**
     * @var array<string, mixed>
     */
    private array $settings;

    /**
     * @param array<string, mixed> $settings
     */
    public function __construct(array $settings)
    {
        $this->settings = $settings;
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @return array<string, mixed>
     */
    public function route(int $orderId, string $gateway, array $gatewayData, array $processorPayload): array
    {
        $slug = sanitize_key($gateway);
        $handlers = $this->handlerMap();

        $filtered = apply_filters(
            'h4bs_processor_router_handlers',
            $handlers,
            $this->settings,
            $slug,
            $gatewayData,
            $processorPayload
        );
        if (is_array($filtered)) {
            $handlers = $filtered;
        }

        $handler = $handlers[$slug] ?? ($handlers['default'] ?? [$this, 'handleDefault']);
        if (! is_callable($handler)) {
            $handler = [$this, 'handleDefault'];
        }

        $result = call_user_func($handler, $orderId, $slug, $gatewayData, $processorPayload, $this->settings);
        if (! is_array($result)) {
            $result = [];
        }

        $configured = ! empty($gatewayData['configured']);
        $result['route'] = sanitize_key((string) ($result['route'] ?? $slug));
        $result['configured'] = $configured;
        $result['gateway_slug'] = $slug;
        if (! isset($result['processor_endpoint']) || ! is_string($result['processor_endpoint'])) {
            $result['processor_endpoint'] = esc_url_raw((string) ($gatewayData['processor_endpoint'] ?? ''));
        }

        if (! isset($result['next_step']) || ! is_string($result['next_step'])) {
            $result['next_step'] = $configured
                ? 'Send this checkout payload through your Help4 payment router endpoint.'
                : 'Configure this gateway in Help4 Commerce > Gateways before accepting checkout.';
        }

        $filteredResult = apply_filters(
            'h4bs_processor_router_result',
            $result,
            $orderId,
            $slug,
            $gatewayData,
            $processorPayload,
            $this->settings
        );
        if (is_array($filteredResult)) {
            $result = $filteredResult;
        }

        return $result;
    }

    /**
     * @return array<string, callable>
     */
    private function handlerMap(): array
    {
        return [
            'stripe_router' => [$this, 'handleStripeRouter'],
            'paypal' => [$this, 'handlePaypal'],
            'square' => [$this, 'handleSquare'],
            'braintree' => [$this, 'handleBraintree'],
            'hosted_payments' => [$this, 'handleHostedPayments'],
            'godaddy_payments' => [$this, 'handleHostedPayments'],
            'authorize_net' => [$this, 'handleAuthorizeNet'],
            'wise' => [$this, 'handleWise'],
            'default' => [$this, 'handleDefault'],
        ];
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleStripeRouter(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'stripe_router',
            'stripe_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'api_track' => sanitize_key((string) ($settings['stripe_api_track'] ?? 'basil')),
                'api_version_override' => sanitize_text_field((string) ($settings['stripe_api_version_override'] ?? '')),
                'embedded_checkout_enabled' => ! empty($settings['stripe_embedded_checkout_enabled']),
                'customer_data_mode' => sanitize_key((string) ($settings['commerce_customer_data_mode'] ?? 'both')),
                'expects_webhooks' => true,
                'account_configured' => $this->settingHasValue('stripe_publishable_key') && $this->settingHasValue('stripe_secret_key'),
            ],
            'Route payment intent/session creation through your Help4 Stripe Router, then confirm checkout via webhooks.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handlePaypal(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'paypal',
            'paypal_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'account_configured' => $this->settingHasValue('paypal_client_id'),
            ],
            'Create or capture PayPal orders through your Help4 payment router adapter.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleSquare(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'square',
            'square_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'account_configured' => $this->settingHasValue('square_application_id'),
            ],
            'Create Square checkout/payment through your Help4 payment router endpoint.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleBraintree(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'braintree',
            'braintree_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'account_configured' => $this->settingHasValue('braintree_merchant_id'),
            ],
            'Create and settle Braintree transactions through your Help4 payment router endpoint.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleHostedPayments(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'hosted_payments',
            'hosted_payments_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'account_configured' => $this->settingHasValue('hosted_payments_profile') || $this->settingHasValue('godaddy_payments_profile'),
            ],
            'Submit transactions to your hosted payment router endpoint and reconcile status from callbacks.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleAuthorizeNet(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'authorize_net',
            'authorize_net_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'account_configured' => $this->settingHasValue('authorize_net_login_id'),
            ],
            'Process via your Authorize.Net router endpoint and update order state from response/webhooks.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleWise(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        return $this->runGatewayRoute(
            $orderId,
            'wise',
            'wise_router_endpoint',
            $gatewayData,
            $processorPayload,
            [
                'account_configured' => $this->settingHasValue('wise_profile_id'),
            ],
            'Generate Wise transfer instructions through your Help4 payment router endpoint.'
        );
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function handleDefault(int $orderId, string $slug, array $gatewayData, array $processorPayload, array $settings): array
    {
        $endpoint = esc_url_raw((string) ($gatewayData['processor_endpoint'] ?? ''));
        if ($endpoint !== '') {
            $dispatch = $this->dispatchToEndpoint($orderId, $slug, $endpoint, $gatewayData, $processorPayload);
            return [
                'route' => $slug !== '' ? $slug : 'default',
                'processor_mode' => 'help4_router_endpoint',
                'processor_endpoint' => $endpoint,
                'dispatch' => $dispatch,
                'next_step' => ! empty($dispatch['success'])
                    ? 'Payment router accepted checkout payload. Continue with processor-side authorization/capture flow.'
                    : 'Payment router dispatch failed. Inspect dispatch.message/http_code for details.',
            ];
        }

        return [
            'route' => $slug !== '' ? $slug : 'default',
            'processor_mode' => 'router_adapter',
            'processor_endpoint' => '',
            'dispatch' => [
                'attempted' => false,
                'success' => false,
                'message' => 'No built-in payment router adapter registered for this gateway.',
            ],
            'next_step' => 'Attach a processor adapter to h4bs_processor_router_handlers or h4bs_process_payment to complete gateway execution.',
        ];
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @param array<string, mixed> $extras
     * @return array<string, mixed>
     */
    private function runGatewayRoute(
        int $orderId,
        string $route,
        string $endpointSettingKey,
        array $gatewayData,
        array $processorPayload,
        array $extras,
        string $defaultNextStep
    ): array {
        $endpoint = esc_url_raw((string) ($this->settings[$endpointSettingKey] ?? ''));
        if ($endpoint === '') {
            $endpoint = esc_url_raw((string) ($gatewayData['processor_endpoint'] ?? ''));
        }
        $dispatch = [
            'attempted' => false,
            'success' => false,
            'http_code' => 0,
            'message' => $endpoint === '' ? 'Processor endpoint is missing.' : 'Dispatch not attempted.',
            'duration_ms' => 0,
        ];

        if ($endpoint !== '') {
            $dispatch = $this->dispatchToEndpoint($orderId, $route, $endpoint, $gatewayData, $processorPayload);
        }

        $result = [
            'route' => sanitize_key($route),
            'processor_mode' => 'help4_router_endpoint',
            'processor_endpoint' => $endpoint,
            'dispatch' => $dispatch,
            'next_step' => $defaultNextStep,
        ];
        foreach ($extras as $key => $value) {
            $result[sanitize_key((string) $key)] = $value;
        }

        $remote = $dispatch['parsed'] ?? null;
        if (is_array($remote)) {
            $remoteStatus = sanitize_key((string) ($remote['status'] ?? ''));
            if ($remoteStatus !== '') {
                $result['remote_status'] = $remoteStatus;
            }
            if (isset($remote['next_step']) && is_string($remote['next_step']) && $remote['next_step'] !== '') {
                $result['next_step'] = sanitize_text_field($remote['next_step']);
            } elseif (! empty($dispatch['success'])) {
                $result['next_step'] = 'Payment router accepted checkout payload. Continue with processor-side authorization/capture flow.';
            } elseif ($endpoint !== '') {
                $result['next_step'] = 'Payment router dispatch failed. Inspect dispatch.message/http_code for details.';
            }

            foreach (['transaction_id', 'payment_intent', 'client_secret', 'checkout_url', 'redirect_url'] as $field) {
                if (! isset($remote[$field])) {
                    continue;
                }
                $value = (string) $remote[$field];
                if (in_array($field, ['checkout_url', 'redirect_url'], true)) {
                    $result[$field] = esc_url_raw($value);
                } else {
                    $result[$field] = sanitize_text_field($value);
                }
            }

            if (array_key_exists('paid', $remote)) {
                $result['paid'] = rest_sanitize_boolean($remote['paid']);
            }
            if (isset($remote['order_status'])) {
                $result['order_status'] = sanitize_key((string) $remote['order_status']);
            }
        } elseif ($endpoint !== '') {
            $result['next_step'] = ! empty($dispatch['success'])
                ? 'Payment router accepted checkout payload. Continue with processor-side authorization/capture flow.'
                : 'Payment router dispatch failed. Inspect dispatch.message/http_code for details.';
        }

        return $result;
    }

    /**
     * @param array<string, mixed> $gatewayData
     * @param array<string, mixed> $processorPayload
     * @return array<string, mixed>
     */
    private function dispatchToEndpoint(
        int $orderId,
        string $route,
        string $endpoint,
        array $gatewayData,
        array $processorPayload
    ): array {
        $startedAt = microtime(true);
        $timestamp = time();
        $siteUrl = home_url('/');
        $host = (string) (wp_parse_url($siteUrl, PHP_URL_HOST) ?: '');
        $rootDomain = $this->rootDomainFromHost($host);
        $sharedSecret = sanitize_text_field((string) ($this->settings['payment_router_shared_secret'] ?? ''));
        $signature = '';
        if ($sharedSecret !== '') {
            $signature = hash_hmac('sha256', $rootDomain . '|' . $route . '|' . (string) $orderId . '|' . (string) $timestamp, $sharedSecret);
        }

        $requestPayload = [
            'gateway' => sanitize_key($route),
            'product_id' => 'help4-builder-suite',
            'timestamp' => $timestamp,
            'order_id' => $orderId,
            'order' => is_array($processorPayload['order'] ?? null) ? $processorPayload['order'] : [],
            'customer' => is_array($processorPayload['customer'] ?? null) ? $processorPayload['customer'] : [],
            'line_items' => is_array($processorPayload['line_items'] ?? null) ? $processorPayload['line_items'] : [],
            'contains_digital' => ! empty($processorPayload['contains_digital']),
            'contains_shippable' => ! empty($processorPayload['contains_shippable']),
            'contains_booking' => ! empty($processorPayload['contains_booking']),
            'contains_service' => ! empty($processorPayload['contains_service']),
            'contains_subscription' => ! empty($processorPayload['contains_subscription']),
            'subscription_count' => max(0, (int) ($processorPayload['subscription_count'] ?? 0)),
            'subscription_candidates' => is_array($processorPayload['subscription_candidates'] ?? null)
                ? $processorPayload['subscription_candidates']
                : [],
            'fulfillment_summary' => is_array($processorPayload['fulfillment_summary'] ?? null)
                ? $processorPayload['fulfillment_summary']
                : [],
            'processor_payload' => $processorPayload,
            'gateway_data' => [
                'slug' => sanitize_key((string) ($gatewayData['slug'] ?? $route)),
                'label' => sanitize_text_field((string) ($gatewayData['label'] ?? '')),
                'configured' => ! empty($gatewayData['configured']),
                'account_configured' => ! empty($gatewayData['account_configured']),
            ],
            'meta' => [
                'site_url' => esc_url_raw((string) $siteUrl),
                'root_domain' => sanitize_text_field($rootDomain),
                'server_ip' => sanitize_text_field(IpTools::currentIp()),
                'wp_version' => sanitize_text_field((string) get_bloginfo('version')),
                'php_version' => sanitize_text_field(PHP_VERSION),
                'plugin_version' => defined('H4BS_VERSION') ? sanitize_text_field((string) H4BS_VERSION) : '',
            ],
        ];
        if ($signature !== '') {
            $requestPayload['signature'] = $signature;
            $requestPayload['signature_algo'] = 'hmac-sha256';
        }

        $headers = [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json; charset=utf-8',
            'X-H4BS-Gateway' => sanitize_key($route),
            'X-H4BS-Timestamp' => (string) $timestamp,
        ];
        if ($signature !== '') {
            $headers['X-H4BS-Signature'] = $signature;
        }

        $requestArgs = [
            'timeout' => self::REQUEST_TIMEOUT,
            'redirection' => 2,
            'headers' => $headers,
            'body' => wp_json_encode($requestPayload),
        ];
        $requestArgs = apply_filters(
            'h4bs_payment_router_request_args',
            $requestArgs,
            $endpoint,
            $route,
            $orderId,
            $processorPayload,
            $gatewayData
        );
        if (! is_array($requestArgs)) {
            $requestArgs = [];
        }

        $response = wp_remote_post($endpoint, $requestArgs);
        $result = $this->normalizeDispatchResponse($response, $startedAt);
        $result['endpoint'] = esc_url_raw($endpoint);
        $result['request_payload'] = [
            'gateway' => sanitize_key($route),
            'timestamp' => $timestamp,
            'signature_attached' => $signature !== '',
            'root_domain' => sanitize_text_field($rootDomain),
        ];

        $filtered = apply_filters(
            'h4bs_payment_router_dispatch_result',
            $result,
            $endpoint,
            $route,
            $orderId,
            $processorPayload,
            $gatewayData
        );

        return is_array($filtered) ? $filtered : $result;
    }

    /**
     * @param array<string, mixed>|\WP_Error $response
     * @return array<string, mixed>
     */
    private function normalizeDispatchResponse($response, float $startedAt): array
    {
        $duration = (int) round((microtime(true) - $startedAt) * 1000);

        if (is_wp_error($response)) {
            return [
                'attempted' => true,
                'success' => false,
                'http_code' => 0,
                'duration_ms' => $duration,
                'message' => sanitize_text_field($response->get_error_message()),
                'error' => sanitize_text_field($response->get_error_code()),
                'parsed' => null,
                'raw_body' => '',
            ];
        }

        $httpCode = (int) wp_remote_retrieve_response_code($response);
        $rawBody = (string) wp_remote_retrieve_body($response);
        $decoded = json_decode($rawBody, true);
        $parsed = is_array($decoded) ? $decoded : null;
        $message = '';
        if (is_array($parsed) && isset($parsed['message'])) {
            $message = sanitize_text_field((string) $parsed['message']);
        }
        if ($message === '') {
            $message = $httpCode > 0
                ? sprintf('HTTP %d from payment router endpoint.', $httpCode)
                : 'Payment router endpoint returned no status code.';
        }

        return [
            'attempted' => true,
            'success' => $httpCode >= 200 && $httpCode < 300,
            'http_code' => $httpCode,
            'duration_ms' => $duration,
            'message' => $message,
            'error' => '',
            'parsed' => $parsed,
            'raw_body' => $parsed === null ? substr($rawBody, 0, 2000) : '',
        ];
    }

    private function settingHasValue(string $key): bool
    {
        return isset($this->settings[$key]) && trim((string) $this->settings[$key]) !== '';
    }

    private function rootDomainFromHost(string $host): string
    {
        $host = strtolower(trim($host));
        if ($host === '') {
            return '';
        }
        if (filter_var($host, FILTER_VALIDATE_IP)) {
            return $host;
        }

        $parts = array_values(array_filter(explode('.', $host)));
        if (count($parts) <= 2) {
            return $host;
        }

        return $parts[count($parts) - 2] . '.' . $parts[count($parts) - 1];
    }
}
