<?php

namespace App\Services;

use App\Models\Order;
use App\Models\PaymentToken;
use App\Models\Setting;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * Online payment gateway scaffold for EasyPaisa / JazzCash "api" mode.
 *
 * The end-to-end plumbing is real: initiate() creates a PaymentToken and hands
 * back a redirect URL; settle() flips the order's payment_status when the
 * gateway calls back. The two provider-specific pieces — building the signed
 * initiate request and verifying the callback signature — are marked TODO and
 * must be completed against the official EasyPaisa / JazzCash merchant docs
 * using the credentials saved on the admin "Payment methods" page.
 *
 * @see docs/MESSAGING_AND_PAYMENTS_SETUP.md
 */
class OnlinePaymentService
{
    public const GATEWAYS = ['easypaisa', 'jazzcash'];

    /** True when the gateway is active AND configured for the online (api) flow. */
    public function isOnline(string $gateway): bool
    {
        if (! in_array($gateway, self::GATEWAYS, true)) {
            return false;
        }

        return Setting::get("pay_{$gateway}_active") === '1'
            && Setting::get("pay_{$gateway}_mode", 'manual') === 'api';
    }

    /**
     * Start an online payment. Returns the data the app/web needs to redirect
     * the customer to the gateway.
     *
     * @return array{gateway:string, token:string, redirect_url:string, env:string}
     */
    public function initiate(Order $order, string $gateway): array
    {
        abort_unless($this->isOnline($gateway), 422, "Online {$gateway} payment is not enabled.");
        abort_if($order->payment_status === 'paid', 422, 'This order is already paid.');

        $token = (string) Str::uuid();

        PaymentToken::create([
            'order_id'   => $order->id,
            'gateway'    => $gateway,
            'token'      => $token,
            'status'     => 'pending',
            'expires_at' => now()->addMinutes(30),
        ]);

        // TODO(provider): build and sign the real initiate request here using the
        // admin Settings — pay_{$gateway}_store_id / _merchant_id / _hash_key /
        // _integrity_salt and pay_{$gateway}_env (sandbox|live) — then return the
        // gateway's hosted-checkout URL instead of our own return URL below.
        $base = rtrim((string) (config('app.payments_return_base_url') ?: config('app.url')), '/');
        $redirectUrl = "{$base}/api/payment/return/{$gateway}?token={$token}";

        return [
            'gateway'      => $gateway,
            'token'        => $token,
            'redirect_url' => $redirectUrl,
            'env'          => Setting::get("pay_{$gateway}_env", 'sandbox'),
        ];
    }

    /**
     * Settle a payment from a gateway callback / return. Idempotent: a token is
     * only acted on once while it is still 'pending'.
     */
    public function settle(string $gateway, string $token, bool $success, ?string $providerTxnId = null): ?Order
    {
        $pt = PaymentToken::where('gateway', $gateway)->where('token', $token)->first();

        if (! $pt || $pt->status !== 'pending') {
            return null;
        }

        // TODO(provider): before trusting `$success`, verify the gateway's
        // signature/checksum on the raw callback payload using the merchant
        // secret (hash_key / integrity_salt). Reject the request if it fails.

        if ($pt->isExpired()) {
            $pt->update(['status' => 'expired']);
            return null;
        }

        return DB::transaction(function () use ($pt, $success, $providerTxnId) {
            $order = $pt->order;

            if ($success) {
                $pt->update(['status' => 'paid', 'provider_txn_id' => $providerTxnId]);
                $order->update([
                    'payment_status'    => 'paid',
                    'payment_reference' => $providerTxnId ?: $pt->token,
                ]);
            } else {
                $pt->update(['status' => 'failed', 'provider_txn_id' => $providerTxnId]);
                $order->update(['payment_status' => 'failed']);
            }

            return $order->fresh();
        });
    }
}
