<?php
namespace WalletUpPro\Services;

use WalletUpPro\Core\BaseSingleton;
use WalletUpPro\Core\Error\ErrorHandler;
use WalletUpPro\Core\Payments\MerchantProvisioning;
use Exception;

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

/**
 * Main Payment Processing Service
 * Handles payment initialization, status tracking, and completion
 */
class PaymentProcessor extends BaseSingleton
{
    private $errorHandler;
    private $table;

    protected function initialize(): void
    {
        global $wpdb;
        $this->errorHandler = ErrorHandler::getInstance();
        $this->table = $wpdb->prefix . 'walletup_payment_transactions';
    }

    /**
     * Initialize a new payment session
     *
     * @param array $data Payment data
     * @return array Payment session info
     * @throws Exception
     */
    public function initializePayment(array $data): array
    {
        global $wpdb;

        try {
            // Generate unique identifiers
            $paymentId = $this->generatePaymentId();
            $paymentHash = $this->generatePaymentHash($data);
            $sessionId = $this->generateSessionId();

            // Validate required fields
            $this->validatePaymentData($data);

            // Derive payment method from metadata or direct field
            $paymentMethod = $data['payment_method']
                ?? $data['metadata']['payment_method']
                ?? '';

            // Prepare transaction record
            $transaction = [
                'payment_id' => $paymentId,
                'payment_hash' => $paymentHash,
                'session_id' => $sessionId,
                'payment_method' => sanitize_text_field($paymentMethod),
                'customer_email' => sanitize_email($data['email']),
                'customer_first_name' => sanitize_text_field($data['first'] ?? ''),
                'customer_last_name' => sanitize_text_field($data['last'] ?? ''),
                'customer_phone' => sanitize_text_field($data['phone'] ?? ''),
                'amount' => floatval($data['amt']),
                'currency' => sanitize_text_field($data['currency'] ?? 'USD'),
                'description' => sanitize_text_field($data['description'] ?? ''),
                'status' => 'pending',
                'metadata' => wp_json_encode($data['metadata'] ?? []),
                'ip_address' => $this->getClientIP(),
                'user_agent' => sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'] ?? '')) ?? '',
                'created_at' => current_time('mysql'),
                'expires_at' => gmdate('Y-m-d H:i:s', strtotime('+24 hours')),
                'wp_user_id' => get_current_user_id() ?: null,
                'wc_order_id' => isset($data['wc_order_id']) ? absint($data['wc_order_id']) : null,
                'wpforms_entry_id' => isset($data['wpforms_entry_id']) ? absint($data['wpforms_entry_id']) : null
            ];

            // Insert into database
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Write operation, caching not applicable.
            $result = $wpdb->insert($this->table, $transaction);

            if ($result === false) {
                throw new Exception(esc_html('Failed to create payment record: ' . $wpdb->last_error));
            }

            // Only call the backend for payment methods that need hosted processing
            // (mobile money, future Stripe Connect / PayPal Commerce via our platform).
            // P2P methods (traditional + crypto) work entirely within WordPress — no
            // external dependency, no added latency, no customer data sent off-site.
            $paymentUrl = '';
            if ($this->requiresBackendProcessing($paymentMethod)) {
                $backendSession = $this->createBackendPaymentSession($paymentId, $transaction);
                if (!empty($backendSession['payment_url'])) {
                    $paymentUrl = $this->getBackendBaseUrl() . $backendSession['payment_url'];
                }
            }

            return [
                'success' => true,
                'payment_id' => $paymentId,
                'payment_hash' => $paymentHash,
                'session_id' => $sessionId,
                'payment_url' => $paymentUrl,
                'expires_at' => $transaction['expires_at']
            ];

        } catch (Exception $e) {
            $this->errorHandler->log('Payment initialization error: ' . $e->getMessage());
            throw $e;
        }
    }

    public function getPaymentStatus(string $paymentId): ?object
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Fresh data required for accurate results.
        $payment = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely derived from wpdb prefix.
            "SELECT * FROM {$this->table} WHERE payment_id = %s",
            $paymentId
        ));

        if ($payment && $payment->metadata) {
            $payment->metadata = json_decode($payment->metadata, true);
        }

        return $payment;
    }

    public function updatePaymentStatus(string $paymentId, string $status, array $data = []): bool
    {
        global $wpdb;

        $update = [
            'status' => $status,
            'updated_at' => current_time('mysql')
        ];

        // Add completion timestamp if completed
        if ($status === 'completed') {
            $update['completed_at'] = current_time('mysql');
        }

        // Add optional fields
        if (isset($data['provider_transaction_id'])) {
            $update['provider_transaction_id'] = $data['provider_transaction_id'];
        }

        if (isset($data['payment_method'])) {
            $update['payment_method'] = $data['payment_method'];
        }

        if (isset($data['payment_provider'])) {
            $update['payment_provider'] = $data['payment_provider'];
        }

        if (isset($data['provider_response'])) {
            $update['provider_response'] = wp_json_encode($data['provider_response']);
        }

        if (isset($data['fee_amount'])) {
            $update['fee_amount'] = floatval($data['fee_amount']);
            $update['net_amount'] = floatval($data['amount']) - floatval($data['fee_amount']);
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Write operation, caching not applicable.
        $result = $wpdb->update(
            $this->table,
            $update,
            ['payment_id' => $paymentId],
            array_fill(0, count($update), '%s'),
            ['%s']
        );

        if ($result !== false) {
            $this->errorHandler->log("Payment {$paymentId} status updated to: {$status}");

            // Trigger WordPress action
            do_action('walletup_payment_status_changed', $paymentId, $status, $data);

            return true;
        }

        return false;
    }

    /**
     * Process webhook data
     */
    public function processWebhook(string $source, array $webhookData): bool
    {
        try {
            // Find payment by provider transaction ID or payment ID
            $payment = $this->findPaymentByWebhook($source, $webhookData);

            if (!$payment) {
                throw new Exception(esc_html('Payment not found for webhook'));
            }

            // Update webhook tracking
            global $wpdb;
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Write operation, caching not applicable.
            $wpdb->update(
                $this->table,
                [
                    'webhook_received_at' => current_time('mysql'),
                    'webhook_attempts' => $payment->webhook_attempts + 1,
                    'webhook_data' => wp_json_encode($webhookData)
                ],
                ['payment_id' => $payment->payment_id]
            );

            // Process based on webhook status
            $this->handleWebhookStatus($payment, $source, $webhookData);

            return true;

        } catch (Exception $e) {
            $this->errorHandler->log("Webhook processing error: {$e->getMessage()}");
            return false;
        }
    }

    /**
     * Determine if a payment method requires the pay.walletup.app backend.
     *
     * Mobile money methods need hosted processing on our backend.
     * Traditional (bank, CashApp, Venmo, Zelle, PayPal.me) and crypto (BTC, ETH, etc.)
     * are P2P — the plugin handles them entirely within WordPress via QR codes / links.
     */
    private function requiresBackendProcessing(string $paymentMethod): bool
    {
        // Mobile money methods always need the backend
        if (class_exists(\WalletUpPro\Core\Config\PaymentMethodsConfig::class)) {
            if (\WalletUpPro\Core\Config\PaymentMethodsConfig::isMobileMoney($paymentMethod)) {
                return true;
            }
        }

        // Explicit list matching PaymentMethodsConfig mobile_money IDs
        $backendMethods = [
            'mpesa_ke', 'orange_money_ci', 'mtn_momo_ci', 'moov_money_ci',
            'mtn_momo_zm', 'airtel_money_zm', 'zamtel_kwacha', 'wave_ci',
        ];

        return in_array($paymentMethod, $backendMethods, true);
    }

    private function getBackendBaseUrl(): string
    {
        return defined('WALLETUP_PAYMENT_BACKEND_URL')
            ? WALLETUP_PAYMENT_BACKEND_URL
            : 'https://pay.walletup.app';
    }

    private function getPaymentUrl(string $paymentId): string
    {
        return $this->getBackendBaseUrl() . "/pay/{$paymentId}";
    }

    private function generatePaymentId(): string
    {
        return 'wup_' . bin2hex(random_bytes(16));
    }

    /**
     * Generate payment hash for security
     */
    private function generatePaymentHash(array $data): string
    {
        $secret = defined('WALLET_UP_LINK_SECRET')
            ? WALLET_UP_LINK_SECRET
            : wp_salt('auth');

        $values = implode('|', [
            $data['email'] ?? '',
            $data['amt'] ?? '',
            time(),
            wp_rand()
        ]);

        return hash_hmac('sha256', $values, $secret);
    }

    private function generateSessionId(): string
    {
        return 'sess_' . bin2hex(random_bytes(16));
    }

    private function validatePaymentData(array $data): void
    {
        $required = ['email', 'amt'];

        foreach ($required as $field) {
            if (empty($data[$field])) {
                throw new Exception(esc_html("Missing required field: {$field}"));
            }
        }

        if (!is_email($data['email'])) {
            throw new Exception(esc_html('Invalid email address'));
        }

        if (!is_numeric($data['amt']) || floatval($data['amt']) <= 0) {
            throw new Exception(esc_html('Invalid amount'));
        }
    }

    private function getClientIP(): string
    {
        $headers = [
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR'
        ];

        foreach ($headers as $header) {
            if (!empty(sanitize_text_field(wp_unslash($_SERVER[$header] ?? '')))) {
                $ips = explode(',', sanitize_text_field(wp_unslash($_SERVER[$header] ?? '')));
                $ip = trim($ips[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }

        return '0.0.0.0';
    }

    /**
     * Find payment by webhook data
     */
    private function findPaymentByWebhook(string $source, array $data): ?object
    {
        global $wpdb;

        if (!empty($data['provider_transaction_id'])) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Fresh data required for accurate results.
            $payment = $wpdb->get_row($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely derived from wpdb prefix.
                "SELECT * FROM {$this->table}
                 WHERE provider_transaction_id = %s
                 AND payment_provider = %s",
                $data['provider_transaction_id'],
                $source
            ));

            if ($payment) {
                return $payment;
            }
        }

        // Try by payment ID
        if (!empty($data['payment_id'])) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Fresh data required for accurate results.
            return $wpdb->get_row($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely derived from wpdb prefix.
                "SELECT * FROM {$this->table} WHERE payment_id = %s",
                $data['payment_id']
            ));
        }

        return null;
    }

    /**
     * Handle webhook status updates
     */
    private function handleWebhookStatus(object $payment, string $source, array $data): void
    {
        $status = $data['status'] ?? 'unknown';

        switch ($status) {
            case 'completed':
            case 'success':
                $this->updatePaymentStatus(
                    $payment->payment_id,
                    'completed',
                    $data
                );
                break;

            case 'failed':
            case 'declined':
                $this->updatePaymentStatus(
                    $payment->payment_id,
                    'failed',
                    $data
                );
                break;

            case 'pending':
            case 'processing':
                $this->updatePaymentStatus(
                    $payment->payment_id,
                    'processing',
                    $data
                );
                break;
        }
    }

    public function getPaymentsByStatus(string $status, int $limit = 50): array
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Fresh data required for accurate results.
        $payments = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely derived from wpdb prefix.
            "SELECT * FROM {$this->table}
             WHERE status = %s
             ORDER BY created_at DESC
             LIMIT %d",
            $status,
            $limit
        ));

        return $payments ?: [];
    }

    public function getCustomerPayments(string $email, int $limit = 50): array
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Fresh data required for accurate results.
        $payments = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely derived from wpdb prefix.
            "SELECT * FROM {$this->table}
             WHERE customer_email = %s
             ORDER BY created_at DESC
             LIMIT %d",
            $email,
            $limit
        ));

        return $payments ?: [];
    }

    /**
     * Create a payment session on the pay.walletup.app backend.
     * This makes the /pay/{id} URL functional for the customer.
     * Non-blocking: failures are logged but don't prevent local payment creation.
     */
    private function createBackendPaymentSession(string $paymentId, array $transaction): array
    {
        $apiKey = MerchantProvisioning::getApiKey();
        if (empty($apiKey)) {
            $this->errorHandler->log('Backend session skipped: not provisioned (no API key)');
            return [];
        }

        $backendUrl = $this->getBackendBaseUrl();
        $webhookUrl = home_url('/wp-json/walletup/v1/webhook/backend-notification');

        $response = wp_remote_post($backendUrl . '/api/payment/create-session', [
            'headers' => [
                'Content-Type' => 'application/json',
                'X-API-Key' => $apiKey,
            ],
            'body' => wp_json_encode([
                'payment_id' => $paymentId,
                'amount' => floatval($transaction['amount']),
                'currency' => $transaction['currency'] ?? 'USD',
                'payment_method' => $transaction['payment_method'] ?? '',
                'customer_email' => $transaction['customer_email'] ?? '',
                'description' => $transaction['description'] ?? '',
                'webhook_url' => $webhookUrl,
                'return_url' => home_url('/walletup-payment-return/?payment_id=' . $paymentId),
                'wc_order_id' => $transaction['wc_order_id'] ?? null,
                'wpforms_entry_id' => $transaction['wpforms_entry_id'] ?? null,
                'expires_at' => $transaction['expires_at'] ?? '',
            ]),
            'timeout' => 10,
        ]);

        if (is_wp_error($response)) {
            $this->errorHandler->log('Backend session creation failed: ' . $response->get_error_message());
            return [];
        }

        $code = wp_remote_retrieve_response_code($response);
        $body = json_decode(wp_remote_retrieve_body($response), true);

        if ($code === 200 || $code === 201) {
            return $body ?? [];
        }

        $this->errorHandler->log('Backend session creation failed: HTTP ' . $code . ' — ' . ($body['error'] ?? 'Unknown'));
        return [];
    }

    public static function backendRequest(string $endpoint, string $method = 'GET', array $body = []): ?array
    {
        $apiKey = MerchantProvisioning::getApiKey();
        if (empty($apiKey)) {
            return null;
        }

        $backendUrl = defined('WALLETUP_PAYMENT_BACKEND_URL')
            ? WALLETUP_PAYMENT_BACKEND_URL
            : 'https://pay.walletup.app';

        $args = [
            'headers' => [
                'Content-Type' => 'application/json',
                'X-API-Key' => $apiKey,
            ],
            'timeout' => 15,
        ];

        if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH') {
            $args['method'] = $method;
            $args['body'] = wp_json_encode($body);
            $response = wp_remote_post($backendUrl . $endpoint, $args);
        } else {
            $response = wp_remote_get($backendUrl . $endpoint, $args);
        }

        if (is_wp_error($response)) {
            return null;
        }

        return json_decode(wp_remote_retrieve_body($response), true);
    }

    /**
     * Clean expired pending payments
     */
    public function cleanExpiredPayments(): int
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Direct query required, caching not applicable.
        $result = $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is safely derived from wpdb prefix.
            "UPDATE {$this->table}
             SET status = 'expired', updated_at = NOW()
             WHERE status = 'pending'
             AND expires_at < NOW()"
        );

        if ($result) {
            $this->errorHandler->log("Cleaned {$result} expired payments");
        }

        return $result ?: 0;
    }
}
