<?php

namespace App\Modules\Payment\GoCardless;

use App\Core\Payment\AbstractPayment;

class GoCardless extends AbstractPayment
{
    protected $apiUrl;

    public function __construct(array $config = [])
    {
        parent::__construct($config);
        $this->apiUrl = $this->isTestMode()
            ? 'https://api-sandbox.gocardless.com'
            : 'https://api.gocardless.com';
    }

    /**
     * Validate payment parameters and return readiness status
     */
    public function attemptPayment(array $params)
    {
        $requiredParams = ['amount', 'currency', 'order_id'];

        foreach ($requiredParams as $param) {
            if (! isset($params[$param]) || empty($params[$param])) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.gocardless_missing_param', ['param' => $param]),
                ];
            }
        }

        return [
            'status' => 'ready',
            'message' => __('admin/modules.gocardless_payment_ready'),
            'data' => [
                'amount' => $params['amount'],
                'currency' => $params['currency'],
                'order_id' => $params['order_id'],
                'has_mandate' => ! empty($params['mandate_id']),
            ],
        ];
    }

    /**
     * Create a payment using existing mandate or redirect to set up a new mandate
     */
    public function makePayment(array $params)
    {
        // If a mandate exists, create a payment directly via API
        // Ref: WHMCS gocardless.php:capture — POST payments{amount,currency,description,metadata,links.mandate}
        if (! empty($params['mandate_id'])) {
            $paymentData = [
                'payments' => [
                    'amount' => (int) round($params['amount'] * 100),
                    'currency' => strtoupper($params['currency'] ?? $this->getConfig('currency', 'GBP')),
                    'links' => [
                        'mandate' => $params['mandate_id'],
                    ],
                    'description' => $params['description'] ?? __('admin/modules.gocardless_payment_description', ['order_id' => $params['order_id']]),
                    'metadata' => [
                        'order_id' => (string) $params['order_id'],
                        'invoice_id' => (string) ($params['invoice_id'] ?? ''),
                    ],
                ],
            ];

            // Idempotency = HTTP header (resmi API), payload alanı DEĞİL.
            $headers = [];
            if (! empty($params['idempotency_key'])) {
                $headers[] = 'Idempotency-Key: ' . $params['idempotency_key'];
            }

            $response = $this->apiPost($this->apiUrl . '/payments', $paymentData, $headers);

            if ($response['http_code'] === 201 && isset($response['response']['payments'])) {
                $payment = $response['response']['payments'];

                return [
                    'status' => 'success',
                    'message' => __('admin/modules.gocardless_payment_created'),
                    'transaction_id' => $payment['id'],
                    'data' => [
                        'payment_id' => $payment['id'],
                        'payment_status' => $payment['status'],
                        'amount' => $payment['amount'],
                        'currency' => $payment['currency'],
                        'charge_date' => $payment['charge_date'] ?? null,
                    ],
                ];
            }

            return $this->errorResponse($response, 'gocardless_payment_failed');
        }

        // No mandate exists - create a redirect flow to set one up
        // Ref: WHMCS gocardless.php:link — POST redirect_flows{session_token,success_redirect_url,prefilled_customer}
        $response = $this->apiPost($this->apiUrl . '/redirect_flows', $this->buildRedirectFlowData($params, 'gocardless_mandate_description'));

        if ($response['http_code'] === 201 && isset($response['response']['redirect_flows'])) {
            $redirectFlow = $response['response']['redirect_flows'];

            return [
                'status' => 'redirect',
                'message' => __('admin/modules.gocardless_redirect_to_mandate'),
                'redirect_url' => $redirectFlow['redirect_url'],
                'data' => [
                    'redirect_flow_id' => $redirectFlow['id'],
                    'session_token' => $redirectFlow['session_token'] ?? ($params['session_token'] ?? ''),
                ],
            ];
        }

        return $this->errorResponse($response, 'gocardless_redirect_flow_failed');
    }

    /**
     * Handle webhook events from GoCardless (fail-closed signature verification)
     *
     * Wiring sözleşmesi: webhook route/controller şunları doldurmalı —
     *   raw_body          = $request->getContent()                       (imza HAM gövde üstünden)
     *   webhook_signature = $request->header('Webhook-Signature')        (GoCardless HMAC-SHA256 header)
     *   events            = json_decode($request->getContent(), true)['events'] (opsiyonel — yoksa raw_body'den çözülür)
     */
    public function handlePaymentCallback(array $params)
    {
        // FAIL-CLOSED: imza HER ZAMAN ham gövde üstünden doğrulanır.
        // Ref: resmi GoCardless Webhook-Signature = HMAC-SHA256(body, webhook_secret).
        $webhookSecret = (string) $this->getConfig('webhook_secret', '');
        $signature = $params['webhook_signature'] ?? '';
        $body = $params['raw_body'] ?? '';

        // PRC-888 foundation: hesaplanan imzayı secureCompare() ile karşılaştır (hash_equals, null/boş→false).
        $computed = ($webhookSecret !== '' && $body !== '')
            ? $this->hmac('sha256', $body, $webhookSecret)
            : '';

        if (! $this->secureCompare($computed, is_string($signature) ? $signature : '')) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gocardless_invalid_webhook_signature'),
            ];
        }

        // events açıkça verilmediyse doğrulanmış ham gövdeden çöz.
        $events = $params['events'] ?? [];
        if (empty($events) && $body !== '') {
            $decoded = json_decode($body, true);
            $events = (is_array($decoded) && isset($decoded['events'])) ? $decoded['events'] : [];
        }

        if (empty($events)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gocardless_no_webhook_events'),
            ];
        }

        $results = [];

        foreach ($events as $event) {
            $resourceType = $event['resource_type'] ?? '';
            $action = $event['action'] ?? '';
            $links = $event['links'] ?? [];

            switch ($resourceType) {
                case 'payments':
                    $results[] = $this->handlePaymentEvent($action, $links, $event);
                    break;

                case 'mandates':
                    $results[] = $this->handleMandateEvent($action, $links, $event);
                    break;

                default:
                    $results[] = [
                        'status' => 'ignored',
                        'message' => __('admin/modules.gocardless_unhandled_event', ['type' => $resourceType]),
                        'event_id' => $event['id'] ?? '',
                    ];
                    break;
            }
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.gocardless_webhook_processed'),
            'data' => $results,
        ];
    }

    /**
     * Refund a payment
     */
    public function refundPayment(array $params)
    {
        // Ref: resmi GoCardless Refunds API — POST refunds{amount,links.payment,metadata}.
        $refundData = [
            'refunds' => [
                'amount' => isset($params['amount']) ? (int) round($params['amount'] * 100) : null,
                'links' => [
                    'payment' => $params['transaction_id'],
                ],
                'metadata' => [
                    'reason' => (string) ($params['reason'] ?? 'Refund from ProCP'),
                    'order_id' => (string) ($params['order_id'] ?? ''),
                ],
            ],
        ];

        // Remove null amount for full refund
        if ($refundData['refunds']['amount'] === null) {
            unset($refundData['refunds']['amount']);
        }

        // Opsiyonel: total_amount_confirmation (mandate iade-toplamı guard). Bazı hesap ayarlarında 422'yi önler.
        if (isset($params['total_amount_confirmation'])) {
            $refundData['refunds']['total_amount_confirmation'] = (int) round($params['total_amount_confirmation'] * 100);
        }

        $response = $this->apiPost($this->apiUrl . '/refunds', $refundData);

        if ($response['http_code'] === 201 && isset($response['response']['refunds'])) {
            $refund = $response['response']['refunds'];

            return [
                'status' => 'success',
                'message' => __('admin/modules.gocardless_refund_success'),
                'refund_id' => $refund['id'],
                'data' => [
                    'refund_id' => $refund['id'],
                    'amount' => $refund['amount'],
                    'currency' => $refund['currency'],
                ],
            ];
        }

        return $this->errorResponse($response, 'gocardless_refund_failed');
    }

    /**
     * Generate a redirect flow link for new mandate setup
     */
    public function generatePaymentLink(array $params)
    {
        $redirectFlowData = $this->buildRedirectFlowData($params, 'gocardless_mandate_setup');
        $response = $this->apiPost($this->apiUrl . '/redirect_flows', $redirectFlowData);

        if ($response['http_code'] === 201 && isset($response['response']['redirect_flows'])) {
            $redirectFlow = $response['response']['redirect_flows'];

            return [
                'status' => 'success',
                'payment_url' => $redirectFlow['redirect_url'],
                'data' => [
                    'redirect_flow_id' => $redirectFlow['id'],
                    'session_token' => $redirectFlowData['redirect_flows']['session_token'],
                ],
            ];
        }

        return $this->errorResponse($response, 'gocardless_payment_link_failed');
    }

    /**
     * Complete a redirect flow and save the mandate as a payment method
     */
    public function saveCard(array $params)
    {
        if (empty($params['redirect_flow_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gocardless_redirect_flow_id_required'),
            ];
        }

        $result = $this->completeRedirectFlow($params['redirect_flow_id'], $params['session_token'] ?? '');

        if ($result['status'] === 'success') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.gocardless_mandate_saved'),
                'card_token' => $result['mandate_id'],
                'data' => [
                    'mandate_id' => $result['mandate_id'],
                    'customer_id' => $result['customer_id'],
                    'bank_account_id' => $result['bank_account_id'] ?? null,
                ],
            ];
        }

        return $result;
    }

    /**
     * Cancel a mandate (delete saved payment method)
     */
    public function deleteCard(array $params)
    {
        if (empty($params['card_token'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gocardless_mandate_id_required'),
            ];
        }

        $mandateId = $params['card_token'];

        // Ref: WHMCS gocardless_storeremote — POST mandates/{id}/actions/cancel {data.metadata}.
        $response = $this->apiPost($this->apiUrl . '/mandates/' . $mandateId . '/actions/cancel', [
            'data' => [
                'metadata' => [
                    'reason' => (string) ($params['reason'] ?? 'Cancelled by customer'),
                ],
            ],
        ]);

        if ($response['http_code'] === 200 && isset($response['response']['mandates'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.gocardless_mandate_cancelled'),
                'data' => [
                    'mandate_id' => $response['response']['mandates']['id'],
                    'mandate_status' => $response['response']['mandates']['status'],
                ],
            ];
        }

        return $this->errorResponse($response, 'gocardless_mandate_cancel_failed');
    }

    /**
     * Create a payment using a saved mandate
     */
    public function payWithSavedCard(array $params)
    {
        if (empty($params['card_token'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gocardless_mandate_id_required'),
            ];
        }

        $params['mandate_id'] = $params['card_token'];

        return $this->makePayment($params);
    }

    /**
     * Test API connection by fetching creditors
     */
    public function testConnection(array $params = [])
    {
        // Ref: WHMCS gocardless_config — GET creditors (creditor doğrulama akışı).
        $response = $this->apiGet($this->apiUrl . '/creditors');

        if ($response['http_code'] === 200 && isset($response['response']['creditors'])) {
            $creditors = $response['response']['creditors'];
            $creditorName = ! empty($creditors) ? $creditors[0]['name'] : 'Unknown';

            return [
                'status' => 'success',
                'message' => __('admin/modules.gocardless_connection_success'),
                'data' => [
                    'creditor_name' => $creditorName,
                    'creditor_count' => count($creditors),
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.gocardless_connection_failed'),
        ];
    }

    /**
     * Validate module configuration
     */
    public function validateConfig()
    {
        $required = ['access_token', 'webhook_secret'];
        $missing = [];

        foreach ($required as $field) {
            if (empty($this->config[$field])) {
                $missing[] = $field;
            }
        }

        if (! empty($missing)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.gocardless_missing_config', ['fields' => implode(', ', $missing)]),
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.gocardless_config_valid'),
        ];
    }

    /**
     * Return supported features
     */
    public function getFeatures(): array
    {
        return [
            'refund' => true,
            '3d_secure' => false,
            'save_card' => true,
            'recurring' => true,
            'fraud_check' => false,
        ];
    }

    /**
     * Return supported features (alias)
     */
    public function getSupportedFeatures(): array
    {
        return $this->getFeatures();
    }

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'access_token' => [
                'type' => 'password',
                'label' => __('admin/modules.gocardless_access_token'),
                'description' => __('admin/modules.gocardless_access_token_desc'),
                'required' => true,
                'value' => '',
                'placeholder' => 'live_...',
            ],
            'webhook_secret' => [
                'type' => 'password',
                'label' => __('admin/modules.gocardless_webhook_secret'),
                'description' => __('admin/modules.gocardless_webhook_secret_desc'),
                'required' => true,
                'value' => '',
                'placeholder' => 'webhook_secret_...',
            ],
            'currency' => [
                'type' => 'select',
                'label' => __('admin/modules.gocardless_currency'),
                'description' => __('admin/modules.gocardless_currency_desc'),
                'required' => true,
                'value' => 'GBP',
                'options' => [
                    'GBP' => 'GBP - British Pound',
                    'EUR' => 'EUR - Euro',
                    'SEK' => 'SEK - Swedish Krona',
                    'DKK' => 'DKK - Danish Krone',
                    'AUD' => 'AUD - Australian Dollar',
                    'NZD' => 'NZD - New Zealand Dollar',
                    'CAD' => 'CAD - Canadian Dollar',
                    'USD' => 'USD - US Dollar',
                ],
            ],
            'scheme' => [
                'type' => 'select',
                'label' => __('admin/modules.gocardless_scheme'),
                'description' => __('admin/modules.gocardless_scheme_desc'),
                'required' => true,
                'value' => 'bacs',
                'options' => [
                    'bacs' => 'Bacs (UK)',
                    'sepa_core' => 'SEPA Core (Europe)',
                    'autogiro' => 'Autogiro (Sweden)',
                    'becs' => 'BECS (Australia)',
                    'becs_nz' => 'BECS NZ (New Zealand)',
                    'pad' => 'PAD (Canada)',
                    'ach' => 'ACH (United States)',
                ],
            ],
        ]);
    }

    /**
     * Override default headers to add Bearer token auth and GoCardless-Version
     */
    protected function getDefaultHeaders()
    {
        return [
            'Content-Type: application/json',
            'Accept: application/json',
            'Authorization: Bearer ' . $this->getConfig('access_token', ''),
            'GoCardless-Version: 2015-07-06',
            'User-Agent: ProCP Payment Module',
        ];
    }

    /**
     * Build the redirect_flows payload shared by makePayment / generatePaymentLink.
     * Ref: WHMCS gocardless_link — prefilled_customer (address/email/name) UX için geçilir.
     */
    private function buildRedirectFlowData(array $params, string $descKey): array
    {
        $redirectFlows = [
            'description' => $params['description'] ?? __('admin/modules.' . $descKey),
            'session_token' => $params['session_token'] ?? md5(($params['order_id'] ?? '') . microtime(true) . random_int(0, PHP_INT_MAX)),
            'success_redirect_url' => $params['success_url'] ?? '',
            'scheme' => $this->getConfig('scheme', 'bacs'),
        ];

        // WHMCS parity: müşteri banka formunda bilgiyi elle girmesin.
        $prefilled = array_filter([
            'given_name' => $params['first_name'] ?? null,
            'family_name' => $params['last_name'] ?? null,
            'email' => $params['email'] ?? null,
            'address_line1' => $params['address1'] ?? null,
            'city' => $params['city'] ?? null,
            'postal_code' => $params['postcode'] ?? null,
            'region' => $params['state'] ?? null,
            'country_code' => $params['country_code'] ?? null,
        ], static fn ($v) => $v !== null && $v !== '');

        if (! empty($prefilled)) {
            $redirectFlows['prefilled_customer'] = $prefilled;
        }

        return ['redirect_flows' => $redirectFlows];
    }

    /**
     * JSON POST via PRC-888 foundation transport (TLS-verify + timeout on),
     * carrying this module's Bearer + GoCardless-Version headers.
     */
    private function apiPost(string $url, array $data, array $extraHeaders = []): array
    {
        return $this->postJson($url, $data, array_merge($this->getDefaultHeaders(), $extraHeaders));
    }

    /**
     * GET via foundation-equivalent transport (TLS-verify + timeout on).
     * httpPost is POST-only, so GoCardless read endpoints use this curl mirror.
     */
    private function apiGet(string $url): array
    {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPGET => true,
            CURLOPT_HTTPHEADER => $this->getDefaultHeaders(),
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
            CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
        ]);

        $response = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return [
            'http_code' => $httpCode,
            'response' => json_decode((string) $response, true),
        ];
    }

    /**
     * Error envelope WITHOUT leaking raw_response (PCI/PII: mandate/customer/token).
     * Yalnız gateway'in yapılandırılmış error mesajı taşınır.
     */
    private function errorResponse(array $response, string $fallbackKey): array
    {
        return [
            'status' => 'error',
            'message' => $response['response']['error']['message'] ?? __('admin/modules.' . $fallbackKey),
            'data' => [
                'http_code' => $response['http_code'] ?? 0,
                'errors' => $response['response']['error']['errors'] ?? null,
            ],
        ];
    }

    /**
     * Complete a redirect flow to obtain the mandate ID
     */
    private function completeRedirectFlow(string $redirectFlowId, string $sessionToken): array
    {
        // Ref: WHMCS callback/gocardless complete — POST redirect_flows/{id}/actions/complete {data.session_token}.
        $response = $this->apiPost(
            $this->apiUrl . '/redirect_flows/' . $redirectFlowId . '/actions/complete',
            ['data' => ['session_token' => $sessionToken]]
        );

        if ($response['http_code'] === 200 && isset($response['response']['redirect_flows'])) {
            $flow = $response['response']['redirect_flows'];

            return [
                'status' => 'success',
                'mandate_id' => $flow['links']['mandate'] ?? null,
                'customer_id' => $flow['links']['customer'] ?? null,
                'bank_account_id' => $flow['links']['customer_bank_account'] ?? null,
            ];
        }

        return $this->errorResponse($response, 'gocardless_complete_redirect_failed');
    }

    /**
     * Handle payment webhook events
     */
    private function handlePaymentEvent(string $action, array $links, array $event): array
    {
        $paymentId = $links['payment'] ?? '';

        switch ($action) {
            case 'confirmed':
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.gocardless_payment_confirmed'),
                    'transaction_id' => $paymentId,
                    'event_id' => $event['id'] ?? '',
                ];

            case 'paid_out':
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.gocardless_payment_paid_out'),
                    'transaction_id' => $paymentId,
                    'event_id' => $event['id'] ?? '',
                ];

            case 'failed':
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.gocardless_payment_event_failed'),
                    'transaction_id' => $paymentId,
                    'event_id' => $event['id'] ?? '',
                    'cause' => $event['details']['cause'] ?? '',
                    'description' => $event['details']['description'] ?? '',
                ];

            default:
                return [
                    'status' => 'ignored',
                    'message' => __('admin/modules.gocardless_unhandled_payment_action', ['action' => $action]),
                    'transaction_id' => $paymentId,
                    'event_id' => $event['id'] ?? '',
                ];
        }
    }

    /**
     * Handle mandate webhook events
     */
    private function handleMandateEvent(string $action, array $links, array $event): array
    {
        $mandateId = $links['mandate'] ?? '';

        switch ($action) {
            case 'created':
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.gocardless_mandate_created'),
                    'mandate_id' => $mandateId,
                    'event_id' => $event['id'] ?? '',
                ];

            case 'cancelled':
                return [
                    'status' => 'cancelled',
                    'message' => __('admin/modules.gocardless_mandate_event_cancelled'),
                    'mandate_id' => $mandateId,
                    'event_id' => $event['id'] ?? '',
                    'cause' => $event['details']['cause'] ?? '',
                ];

            case 'failed':
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.gocardless_mandate_event_failed'),
                    'mandate_id' => $mandateId,
                    'event_id' => $event['id'] ?? '',
                    'cause' => $event['details']['cause'] ?? '',
                    'description' => $event['details']['description'] ?? '',
                ];

            default:
                return [
                    'status' => 'ignored',
                    'message' => __('admin/modules.gocardless_unhandled_mandate_action', ['action' => $action]),
                    'mandate_id' => $mandateId,
                    'event_id' => $event['id'] ?? '',
                ];
        }
    }
}
