<?php
//Developer: core tech web designTechnology
//Whatsapp: +2349162671360
//Telegram: @jeditech

/**
 * Reloadly Gift Cards API Client
 * Docs: https://developers.reloadly.com/gift-cards/introduction
 *
 * Auth:     OAuth2 client_credentials → Bearer token
 * Sandbox:  https://giftcards-sandbox.reloadly.com
 * Live:     https://giftcards.reloadly.com
 * Token:    https://auth.reloadly.com/oauth/token
 *
 * Token is cached in a flat file for 55 days (live) / 23 hours (sandbox)
 * to avoid hammering the auth endpoint on every request.
 */
class Reloadly {

    private string $client_id;
    private string $client_secret;
    private bool   $sandbox;
    private string $base_url;
    private string $token_cache_file;

    const AUTH_URL     = 'https://auth.reloadly.com/oauth/token';
    const LIVE_URL     = 'https://giftcards.reloadly.com';
    const SANDBOX_URL  = 'https://giftcards-sandbox.reloadly.com';

    public function __construct(
        string $client_id,
        string $client_secret,
        bool   $sandbox = false,
        string $cache_dir = __DIR__ . '/cache'
    ) {
        $this->client_id     = $client_id;
        $this->client_secret = $client_secret;
        $this->sandbox       = $sandbox;
        $this->base_url      = $sandbox ? self::SANDBOX_URL : self::LIVE_URL;

        // Ensure cache dir exists
        if (!is_dir($cache_dir)) {
            @mkdir($cache_dir, 0750, true);
        }
        $this->token_cache_file = rtrim($cache_dir, '/') . '/reloadly_token_' . ($sandbox ? 'sandbox' : 'live') . '.json';
    }

    // ══════════════════════════════════════════════════════════════
    //  AUTH
    // ══════════════════════════════════════════════════════════════

    /**
     * Get a valid Bearer token, using cached value when still valid.
     */
    public function getToken(): string {
        // Use cached token if not expired
        if (file_exists($this->token_cache_file)) {
            $cache = json_decode(file_get_contents($this->token_cache_file), true);
            if (!empty($cache['token']) && !empty($cache['expires_at']) && time() < $cache['expires_at']) {
                return $cache['token'];
            }
        }

        // Fetch new token
        $audience = $this->base_url;
        $payload  = [
            'client_id'     => $this->client_id,
            'client_secret' => $this->client_secret,
            'grant_type'    => 'client_credentials',
            'audience'      => $audience,
        ];

        $ch = curl_init(self::AUTH_URL);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($payload),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 20,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        ]);
        $response = curl_exec($ch);
        $err      = curl_error($ch);
        curl_close($ch);

        if ($err) throw new \RuntimeException('Reloadly auth cURL error: ' . $err);

        $decoded = json_decode($response, true);
        if (empty($decoded['access_token'])) {
            throw new \RuntimeException('Reloadly auth failed: ' . $response);
        }

        // Cache for (expires_in - 300) seconds as safety buffer
        $ttl = (int)($decoded['expires_in'] ?? 86400) - 300;
        file_put_contents($this->token_cache_file, json_encode([
            'token'      => $decoded['access_token'],
            'expires_at' => time() + max($ttl, 0),
        ]));

        return $decoded['access_token'];
    }

    // ══════════════════════════════════════════════════════════════
    //  CORE HTTP
    // ══════════════════════════════════════════════════════════════

    private function get(string $endpoint, array $params = []): array {
        $url = $this->base_url . '/' . ltrim($endpoint, '/');
        if ($params) $url .= '?' . http_build_query($params);

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 30,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER     => [
                'Authorization: Bearer ' . $this->getToken(),
                'Accept: application/com.reloadly.giftcards-v1+json',
            ],
        ]);
        $response  = curl_exec($ch);
        $curl_err  = curl_error($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return $this->parseResponse($response, $curl_err, $http_code);
    }

    private function post(string $endpoint, array $data): array {
        $url = $this->base_url . '/' . ltrim($endpoint, '/');

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($data),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 60,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_HTTPHEADER     => [
                'Authorization: Bearer ' . $this->getToken(),
                'Content-Type: application/json',
                'Accept: application/com.reloadly.giftcards-v1+json',
            ],
        ]);
        $response  = curl_exec($ch);
        $curl_err  = curl_error($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return $this->parseResponse($response, $curl_err, $http_code);
    }

    private function parseResponse(string|false $response, string $curl_err, int $http_code): array {
        if ($curl_err) {
            return ['success' => false, 'error' => 'cURL error: ' . $curl_err, 'code' => 0];
        }
        if (empty($response)) {
            return ['success' => false, 'error' => 'Empty response (HTTP ' . $http_code . ')', 'code' => $http_code];
        }

        $decoded = json_decode($response, true);
        if ($decoded === null) {
            return ['success' => false, 'error' => 'Invalid JSON response', 'raw' => $response, 'code' => $http_code];
        }

        // Reloadly returns HTTP 200/201 on success; errors come with 4xx/5xx + errorCode field
        $ok = $http_code >= 200 && $http_code < 300 && empty($decoded['errorCode']);

        return [
            'success' => $ok,
            'data'    => $decoded,
            'error'   => $ok ? null : ($decoded['message'] ?? $decoded['error'] ?? 'Unknown error from Reloadly'),
            'code'    => $http_code,
        ];
    }

    // ══════════════════════════════════════════════════════════════
    //  PRODUCTS / CATALOG
    // ══════════════════════════════════════════════════════════════

    /**
     * List all gift card products (paginated).
     * @param int  $page
     * @param int  $size   max 200
     */
    public function getProducts(int $page = 1, int $size = 200, array $extra = []): array {
        return $this->get('products', array_merge(['page' => $page, 'size' => $size], $extra));
    }

    /**
     * Get a single product by its productId.
     */
    public function getProductById(int $product_id): array {
        return $this->get("products/{$product_id}");
    }

    /**
     * Get products available in a specific country (ISO2).
     */
    public function getProductsByCountry(string $iso_code, int $page = 1, int $size = 200): array {
        return $this->get("countries/{$iso_code}/products", ['page' => $page, 'size' => $size]);
    }

    // ══════════════════════════════════════════════════════════════
    //  DISCOUNTS
    // ══════════════════════════════════════════════════════════════

    /**
     * List all products that currently have a discount.
     */
    public function getDiscounts(int $page = 1, int $size = 200): array {
        return $this->get('discounts', ['page' => $page, 'size' => $size]);
    }

    /**
     * Get discount details for a specific product.
     */
    public function getDiscountByProduct(int $product_id): array {
        return $this->get("discounts/products/{$product_id}");
    }

    // ══════════════════════════════════════════════════════════════
    //  ORDERS
    // ══════════════════════════════════════════════════════════════

    /**
     * Place a gift card order.
     *
     * @param int    $product_id        Reloadly productId
     * @param string $country_code      ISO2 country code of the product
     * @param float  $unit_price        Face value to purchase (in product currency)
     * @param int    $quantity          Number of codes (usually 1)
     * @param string $recipient_email   Where to send the code
     * @param string $sender_name       Name shown on the gift card
     * @param string $custom_identifier Your internal reference (order id, txn ref, etc.)
     * @param array  $recipient_phone   ['countryCode' => 'NG', 'phoneNumber' => '08012345678']  (optional)
     */
    public function placeOrder(
        int    $product_id,
        string $country_code,
        float  $unit_price,
        int    $quantity,
        string $recipient_email,
        string $sender_name,
        string $custom_identifier,
        array  $recipient_phone = []
    ): array {
        $payload = [
            'productId'        => $product_id,
            'countryCode'      => strtoupper($country_code),
            'quantity'         => $quantity,
            'unitPrice'        => $unit_price,
            'customIdentifier' => $custom_identifier,
            'senderName'       => $sender_name,
            'recipientEmail'   => $recipient_email,
        ];

        if (!empty($recipient_phone)) {
            $payload['recipientPhoneDetails'] = $recipient_phone;
        }

        return $this->post('orders', $payload);
    }

    // ══════════════════════════════════════════════════════════════
    //  REDEEM CODES
    // ══════════════════════════════════════════════════════════════

    /**
     * Get the redeem code/PIN for a completed order transaction.
     * @param int $transaction_id  The transactionId returned from placeOrder()
     */
    public function getRedeemCode(int $transaction_id): array {
        return $this->get("orders/transactions/{$transaction_id}/cards");
    }

    // ══════════════════════════════════════════════════════════════
    //  TRANSACTIONS
    // ══════════════════════════════════════════════════════════════

    /**
     * List all gift card transactions for your account.
     */
    public function getTransactions(int $page = 1, int $size = 20): array {
        return $this->get('orders/transactions', ['page' => $page, 'size' => $size]);
    }

    /**
     * Get a single transaction by ID.
     */
    public function getTransactionById(int $transaction_id): array {
        return $this->get("orders/transactions/{$transaction_id}");
    }

    // ══════════════════════════════════════════════════════════════
    //  BALANCE
    // ══════════════════════════════════════════════════════════════

    public function getBalance(): array {
        return $this->get('accounts/balance');
    }
}