<?php

namespace App\Services;

use App\Models\Order;
use App\Models\Setting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class DigiPayService
{
    public const TICKET_TYPE_UPG = 11;

    public function baseUrl(): string
    {
        $base = Setting::get('digipay_base_url', 'https://api.mydigipay.com/digipay/api');
        return rtrim((string) $base, '/');
    }

    public function isConfigured(): bool
    {
        return Setting::get('digipay_client_id')
            && Setting::get('digipay_client_secret')
            && Setting::get('digipay_username')
            && Setting::get('digipay_password')
            && Setting::get('digipay_base_url')
            && Setting::get('digipay_seller_id');
    }

    /**
     * DigiPay cellNumber expects 09xxxxxxxxx.
     */
    public static function normalizeMobile(?string $mobile): ?string
    {
        if ($mobile === null || $mobile === '') {
            return null;
        }

        $persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
        $arabic  = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
        $mobile  = str_replace($persian, range(0, 9), $mobile);
        $mobile  = str_replace($arabic, range(0, 9), $mobile);
        $mobile  = preg_replace('/\s+/', '', $mobile) ?? '';

        if (preg_match('/^\+980(\d{10})$/', $mobile, $m)) {
            return '0' . $m[1];
        }
        if (preg_match('/^\+98(\d{10})$/', $mobile, $m)) {
            return '0' . $m[1];
        }
        if (preg_match('/^98(\d{10})$/', $mobile, $m)) {
            return '0' . $m[1];
        }
        if (preg_match('/^09\d{9}$/', $mobile)) {
            return $mobile;
        }
        if (preg_match('/^9\d{9}$/', $mobile)) {
            return '0' . $mobile;
        }

        return null;
    }

    public function amountToRial(int|float $toman): int
    {
        return (int) round($toman * 10);
    }

    /**
     * Create UPG ticket and get redirect URL.
     *
     * @return array{success:bool,ticket?:string,redirectUrl?:string,providerId?:string,error?:string,raw?:array}
     */
    public function createPayment(Order $order, string $callbackUrl): array
    {
        if (!$this->isConfigured()) {
            return ['success' => false, 'error' => 'اطلاعات اتصال دیجی‌پی کامل نیست.'];
        }

        $order->loadMissing(['items.product.brand', 'items.product.categories', 'user']);

        $cell = self::normalizeMobile($order->user?->mobile);
        if (!$cell) {
            return ['success' => false, 'error' => 'شماره موبایل کاربر برای دیجی‌پی معتبر نیست.'];
        }

        $providerId = 'dp-' . $order->id . '-' . time();
        $amountRial = $this->amountToRial((float) $order->payable_amount);

        $payload = [
            'cellNumber'       => $cell,
            'amount'           => $amountRial,
            'providerId'       => $providerId,
            'callbackUrl'      => $callbackUrl,
            'basketDetailsDto' => $this->buildBasket($order),
        ];

        $response = null;
        for ($attempt = 1; $attempt <= 3; $attempt++) {
            $response = $this->request('/tickets/business', 'POST', ['type' => self::TICKET_TYPE_UPG], $payload);
            if ($response !== null && (int) ($response['result']['status'] ?? -1) === 0) {
                break;
            }
            if ($attempt < 3) {
                usleep(1000000);
            }
        }

        if ($response === null) {
            return ['success' => false, 'error' => 'خطا در اتصال به درگاه دیجی‌پی.', 'providerId' => $providerId];
        }

        if ((int) ($response['result']['status'] ?? -1) !== 0) {
            return [
                'success'    => false,
                'error'      => $response['result']['message'] ?? 'خطا در ایجاد تیکت پرداخت.',
                'providerId' => $providerId,
                'raw'        => $response,
            ];
        }

        $ticket      = $response['ticket'] ?? null;
        $redirectUrl = $response['redirectUrl'] ?? $response['payUrl'] ?? null;

        if (!$ticket || !$redirectUrl) {
            return [
                'success'    => false,
                'error'      => 'پاسخ درگاه ناقص است.',
                'providerId' => $providerId,
                'raw'        => $response,
            ];
        }

        return [
            'success'     => true,
            'ticket'      => $ticket,
            'redirectUrl' => $redirectUrl,
            'providerId'  => $providerId,
            'raw'         => $response,
        ];
    }

    /**
     * Verify purchase after user returns from DigiPay.
     *
     * @return array{success:bool,error?:string,raw?:array}
     */
    public function verifyPayment(string $trackingCode, string $providerId, int $type): array
    {
        $response = null;
        for ($attempt = 1; $attempt <= 3; $attempt++) {
            $response = $this->request('/purchases/verify', 'POST', ['type' => $type], [
                'trackingCode' => $trackingCode,
                'providerId'   => $providerId,
            ]);
            if ($response !== null) {
                break;
            }
            if ($attempt < 3) {
                usleep(1000000);
            }
        }

        if ($response === null) {
            return ['success' => false, 'error' => 'خطا در تأیید پرداخت دیجی‌پی.'];
        }

        if ((int) ($response['result']['status'] ?? -1) !== 0) {
            return [
                'success' => false,
                'error'   => $response['result']['message'] ?? 'پرداخت تأیید نشد.',
                'raw'     => $response,
            ];
        }

        return ['success' => true, 'raw' => $response];
    }

    /**
     * Refund a paid DigiPay order.
     *
     * @return array{success:bool,message:string,raw?:array}
     */
    public function cancelPayment(Order $order): array
    {
        if ($order->payment_method !== 'digipay') {
            return ['success' => true, 'message' => 'سفارش مربوط به دیجی‌پی نیست.'];
        }

        $tx = $order->relationLoaded('transactions')
            ? $order->transactions->where('gateway', 'digipay')->sortByDesc('id')->first()
            : $order->transactions()->where('gateway', 'digipay')->latest('id')->first();

        $gatewayData = is_array($tx?->gateway_response) ? $tx->gateway_response : [];

        if (!empty($gatewayData['digipay_refunded_api'])) {
            return ['success' => true, 'message' => 'عودت قبلاً در دیجی‌پی ثبت شده است.'];
        }

        $trackingCode = $tx?->ref_number
            ?: ($gatewayData['trackingCode'] ?? null);
        $saleProviderId = $gatewayData['providerId'] ?? $tx?->track_id;

        if (!$trackingCode || !$saleProviderId) {
            return ['success' => false, 'message' => 'کد پیگیری پرداخت برای عودت یافت نشد.'];
        }

        $type = (int) ($gatewayData['payment_type'] ?? self::TICKET_TYPE_UPG);
        $refundProviderId = 'dpr-' . $order->id . '-' . time();
        $amountRial = $this->amountToRial((float) $order->payable_amount);

        $response = null;
        for ($attempt = 1; $attempt <= 3; $attempt++) {
            $response = $this->request('/refunds', 'POST', ['type' => $type], [
                'providerId'        => $refundProviderId,
                'amount'            => $amountRial,
                'saleTrackingCode'  => $trackingCode,
            ]);
            if ($response !== null && (int) ($response['result']['status'] ?? -1) === 0) {
                break;
            }
            if ($attempt < 3) {
                usleep(1000000);
            }
        }

        if ($response === null) {
            return ['success' => false, 'message' => 'خطا در اتصال به API عودت دیجی‌پی.'];
        }

        if ((int) ($response['result']['status'] ?? -1) !== 0) {
            $err = $response['result']['message'] ?? 'عودت ناموفق بود.';
            $this->appendOrderNote($order, 'دیجی‌پی: عودت ناموفق. ' . $err);

            return ['success' => false, 'message' => is_string($err) ? $err : 'عودت ناموفق بود.', 'raw' => $response];
        }

        if ($tx) {
            $tx->update([
                'gateway_response' => array_merge($gatewayData, [
                    'digipay_refunded_api' => true,
                    'refund'               => $response,
                    'refund_provider_id'   => $refundProviderId,
                ]),
            ]);
        }

        $this->appendOrderNote($order, 'دیجی‌پی: عودت با موفقیت ثبت شد.');

        return ['success' => true, 'message' => 'عودت سفارش در دیجی‌پی با موفقیت ثبت شد.', 'raw' => $response];
    }

    /**
     * @return array{success:bool,error?:string}
     */
    public function testConnection(): array
    {
        if (!$this->isConfigured()) {
            return ['success' => false, 'error' => 'اطلاعات اتصال دیجی‌پی کامل نیست.'];
        }

        Cache::forget('digipay_oauth');
        $token = $this->getAccessToken(true);

        return $token
            ? ['success' => true]
            : ['success' => false, 'error' => 'دریافت توکن OAuth ناموفق بود.'];
    }

    /**
     * @return array{basketId:string,items:list<array>}
     */
    private function buildBasket(Order $order): array
    {
        $sellerId    = (string) Setting::get('digipay_seller_id', '');
        $supplierId  = (string) Setting::get('digipay_supplier_id', '') ?: $sellerId;
        $productType = (int) Setting::get('digipay_default_product_type', 1);
        if (!in_array($productType, [1, 2, 3, 4], true)) {
            $productType = 1;
        }

        $items = [];
        foreach ($order->items as $item) {
            $product = $item->product;
            $brand   = $product?->brand?->name ?: ($product?->title_en ?: 'brand');
            $catId   = optional($product?->categories?->first())->id;

            $items[] = [
                'sellerId'    => $sellerId,
                'supplierId'  => $supplierId,
                'productCode' => (string) ($item->variant_id ?: $item->product_id),
                'brand'       => mb_substr((string) $brand, 0, 100),
                'productType' => $productType,
                'count'       => (int) $item->qty,
                'categoryId'  => $catId ? (string) $catId : 'general',
            ];
        }

        if ($items === []) {
            $items[] = [
                'sellerId'    => $sellerId,
                'supplierId'  => $supplierId,
                'productCode' => (string) $order->id,
                'brand'       => 'shop',
                'productType' => $productType,
                'count'       => 1,
                'categoryId'  => 'general',
            ];
        }

        return [
            'basketId' => 'basket-' . $order->id,
            'items'    => $items,
        ];
    }

    private function appendOrderNote(Order $order, string $note): void
    {
        try {
            $existing = trim((string) ($order->notes ?? ''));
            $line     = '[' . now()->format('Y-m-d H:i') . '] ' . $note;
            $order->update([
                'notes' => $existing === '' ? $line : ($existing . "\n" . $line),
            ]);
        } catch (\Throwable $e) {
            // ignore
        }
    }

    /**
     * @param  array<string, mixed>  $query
     * @param  array<string, mixed>|null  $jsonBody
     * @return array<string, mixed>|null
     */
    private function request(string $path, string $method = 'POST', array $query = [], ?array $jsonBody = null): ?array
    {
        $token = $this->getAccessToken();
        if (!$token) {
            return null;
        }

        $url = $this->baseUrl() . '/' . ltrim($path, '/');
        if ($query) {
            $url .= (str_contains($url, '?') ? '&' : '?') . http_build_query($query);
        }

        try {
            $http = Http::timeout(45)
                ->withHeaders([
                    'Authorization'   => 'Bearer ' . $token,
                    'Agent'           => 'WEB',
                    'Digipay-Version' => Setting::get('digipay_api_version', '2022-02-02'),
                    'Accept'          => 'application/json',
                ]);

            $response = strtoupper($method) === 'GET'
                ? $http->get($url)
                : $http->asJson()->post($url, $jsonBody ?? []);

            if ($response->status() === 401) {
                Cache::forget('digipay_oauth');
                $token = $this->getAccessToken(true);
                if (!$token) {
                    return null;
                }
                $http = Http::timeout(45)->withHeaders([
                    'Authorization'   => 'Bearer ' . $token,
                    'Agent'           => 'WEB',
                    'Digipay-Version' => Setting::get('digipay_api_version', '2022-02-02'),
                    'Accept'          => 'application/json',
                ]);
                $response = strtoupper($method) === 'GET'
                    ? $http->get($url)
                    : $http->asJson()->post($url, $jsonBody ?? []);
            }

            $json = $response->json();
            if (!is_array($json)) {
                Log::warning('DigiPay non-JSON response', [
                    'url'    => $url,
                    'status' => $response->status(),
                    'body'   => $response->body(),
                ]);
                return null;
            }

            return $json;
        } catch (\Throwable $e) {
            Log::error('DigiPay request failed: ' . $e->getMessage(), ['url' => $url]);
            return null;
        }
    }

    private function getAccessToken(bool $force = false): ?string
    {
        if (!$force) {
            $cached = Cache::get('digipay_oauth');
            if (is_array($cached) && !empty($cached['access_token'])) {
                return $cached['access_token'];
            }
        }

        $clientId     = (string) Setting::get('digipay_client_id', '');
        $clientSecret = (string) Setting::get('digipay_client_secret', '');
        $username     = (string) Setting::get('digipay_username', '');
        $password     = (string) Setting::get('digipay_password', '');

        if ($clientId === '' || $clientSecret === '' || $username === '' || $password === '') {
            return null;
        }

        $basic = base64_encode($clientId . ':' . $clientSecret);
        $url   = $this->baseUrl() . '/oauth/token';

        try {
            // Try refresh first when forcing renew with existing refresh token
            if ($force) {
                $prev = Cache::get('digipay_oauth');
                if (is_array($prev) && !empty($prev['refresh_token'])) {
                    $refreshed = Http::timeout(30)
                        ->withHeaders(['Authorization' => 'Basic ' . $basic])
                        ->asForm()
                        ->post($url, [
                            'grant_type'    => 'refresh_token',
                            'refresh_token' => $prev['refresh_token'],
                        ]);
                    if ($refreshed->successful() && $refreshed->json('access_token')) {
                        $this->storeOauth($refreshed->json());
                        return $refreshed->json('access_token');
                    }
                }
            }

            $response = Http::timeout(30)
                ->withHeaders(['Authorization' => 'Basic ' . $basic])
                ->asForm()
                ->post($url, [
                    'username'   => $username,
                    'password'   => $password,
                    'grant_type' => 'password',
                ]);

            if (!$response->successful() || !$response->json('access_token')) {
                Log::warning('DigiPay OAuth failed', [
                    'status' => $response->status(),
                    'body'   => $response->body(),
                ]);
                return null;
            }

            $this->storeOauth($response->json());

            return $response->json('access_token');
        } catch (\Throwable $e) {
            Log::error('DigiPay OAuth exception: ' . $e->getMessage());
            return null;
        }
    }

    private function storeOauth(array $data): void
    {
        $expiresIn = (int) ($data['expires_in'] ?? 3500);
        $ttl       = max(60, $expiresIn - 60);

        Cache::put('digipay_oauth', [
            'access_token'  => $data['access_token'] ?? null,
            'refresh_token' => $data['refresh_token'] ?? null,
            'expires_in'    => $expiresIn,
        ], $ttl);
    }
}
