<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class MpesaService
{
    protected string $consumerKey;
    protected string $consumerSecret;
    protected string $shortcode;
    protected ?string $till;
    protected string $stkMode;
    protected string $passkey;
    protected string $baseUrl;
    protected string $stkUrl;
    protected string $callbackUrl;

    public function __construct()
    {
        $this->consumerKey = config('services.mpesa.consumer_key');
        $this->consumerSecret = config('services.mpesa.consumer_secret');
        $this->shortcode = config('services.mpesa.shortcode');
        $this->till = config('services.mpesa.till');
        $this->stkMode = strtolower((string) config('services.mpesa.stk_mode', 'paybill'));
        $this->passkey = config('services.mpesa.passkey');
        $this->baseUrl = config('services.mpesa.base_url');
        $this->stkUrl = config('services.mpesa.stk_url');
        $this->callbackUrl = config('services.mpesa.callback_url');
    }

    public function getAccessToken(): ?string
    {
        try {
            $response = Http::withBasicAuth($this->consumerKey, $this->consumerSecret)
                ->get(config('services.mpesa.auth_url'));

            if ($response->successful()) {
                return $response->json('access_token');
            }

            Log::error('M-Pesa auth failed', ['response' => $response->body()]);
            return null;
        } catch (\Exception $e) {
            Log::error('M-Pesa auth exception', ['message' => $e->getMessage()]);
            return null;
        }
    }

    public function stkPush(string $phone, float $amount, string $accountRef, string $description = 'Payment'): array
    {
        $token = $this->getAccessToken();
        if (!$token) {
            return ['success' => false, 'message' => 'Could not get access token'];
        }

        $timestamp = date('YmdHis');
        $businessShortCode = $this->shortcode;
        $password = base64_encode($businessShortCode . $this->passkey . $timestamp);
        $transactionType = $this->stkMode === 'till' ? 'CustomerBuyGoodsOnline' : 'CustomerPayBillOnline';
        $partyB = $this->stkMode === 'till' && !empty($this->till) ? $this->till : $this->shortcode;

        // Format phone: ensure 254 prefix
        $phone = $this->formatPhone($phone);

        $payload = [
            'BusinessShortCode' => $businessShortCode,
            'Password' => $password,
            'Timestamp' => $timestamp,
            'TransactionType' => $transactionType,
            'Amount' => (int) ceil($amount),
            'PartyA' => $phone,
            'PartyB' => $partyB,
            'PhoneNumber' => $phone,
            'CallBackURL' => $this->callbackUrl,
            'AccountReference' => $accountRef,
            'TransactionDesc' => $description,
        ];

        Log::info('M-Pesa STK request', [
            'mode' => $this->stkMode,
            'payload' => $payload,
        ]);

        try {
            $response = Http::withToken($token)->post($this->stkUrl, $payload);
            Log::info('M-Pesa STK response', [
                'status' => $response->status(),
                'body' => $response->json() ?? $response->body(),
            ]);

            if ($response->successful()) {
                $data = $response->json();
                if (isset($data['ResponseCode']) && $data['ResponseCode'] === '0') {
                    return [
                        'success' => true,
                        'merchant_request_id' => $data['MerchantRequestID'],
                        'checkout_request_id' => $data['CheckoutRequestID'],
                        'response_description' => $data['ResponseDescription'] ?? '',
                    ];
                }
                return ['success' => false, 'message' => $data['ResponseDescription'] ?? 'STK push failed'];
            }

            return ['success' => false, 'message' => 'HTTP request failed: ' . $response->body()];
        } catch (\Exception $e) {
            Log::error('STK Push exception', ['message' => $e->getMessage()]);
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    public function queryTransactionStatus(string $checkoutRequestId): array
    {
        $token = $this->getAccessToken();
        if (!$token) {
            return ['success' => false, 'message' => 'Could not get access token'];
        }

        $timestamp = date('YmdHis');
        $businessShortCode = $this->shortcode;
        $password = base64_encode($businessShortCode . $this->passkey . $timestamp);

        $payload = [
            'BusinessShortCode' => $businessShortCode,
            'Password' => $password,
            'Timestamp' => $timestamp,
            'CheckoutRequestID' => $checkoutRequestId,
        ];

        try {
            $url = $this->baseUrl . '/mpesa/stkpushquery/v1/query';
            $response = Http::withToken($token)->post($url, $payload);

            if ($response->successful()) {
                return ['success' => true, 'data' => $response->json()];
            }

            return ['success' => false, 'message' => $response->body()];
        } catch (\Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    public function getAccountBalance(): array
    {
        $token = $this->getAccessToken();
        if (!$token) {
            return ['success' => false, 'message' => 'Could not get access token'];
        }

        $payload = [
            'Initiator' => 'apitest',
            'SecurityCredential' => 'YOUR_SECURITY_CREDENTIAL',
            'CommandID' => 'AccountBalance',
            'PartyA' => $this->shortcode,
            'IdentifierType' => '4',
            'Remarks' => 'Balance query',
            'QueueTimeOutURL' => $this->callbackUrl,
            'ResultURL' => $this->callbackUrl,
        ];

        try {
            $url = config('services.mpesa.account_balance_url');
            $response = Http::withToken($token)->post($url, $payload);

            if ($response->successful()) {
                return ['success' => true, 'data' => $response->json()];
            }

            return ['success' => false, 'message' => $response->body()];
        } catch (\Exception $e) {
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }

    protected function formatPhone(string $phone): string
    {
        $phone = preg_replace('/[^0-9]/', '', $phone);

        if (str_starts_with($phone, '0')) {
            $phone = '254' . substr($phone, 1);
        } elseif (str_starts_with($phone, '+254')) {
            $phone = substr($phone, 1);
        } elseif (!str_starts_with($phone, '254')) {
            $phone = '254' . $phone;
        }

        return $phone;
    }
}
