<?php

namespace App\Services;

use App\Models\Order;
use App\Models\SiteSetting;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
use Exception;

class MpesaService
{
    protected $consumerKey;
    protected $consumerSecret;
    protected $passkey;
    protected $callbackUrl;
    protected $env;
    protected $businessShortcode;

    public function __construct()
    {
        $this->consumerKey = config('services.mpesa.consumer_key');
        $this->consumerSecret = config('services.mpesa.consumer_secret');
        $this->passkey = config('services.mpesa.passkey');
        $this->callbackUrl = config('services.mpesa.callback_url');
        $this->env = config('services.mpesa.env', 'sandbox');
        $this->businessShortcode = config('services.mpesa.business_shortcode');
    }

    private function getBaseUrl(): string
    {
        return $this->env === 'production'
            ? 'https://api.safaricom.co.ke'
            : 'https://sandbox.safaricom.co.ke';
    }

    public function getAccessToken(): string
    {
        $url = $this->getBaseUrl() . '/oauth/v1/generate?grant_type=client_credentials';

        $response = Http::withBasicAuth($this->consumerKey, $this->consumerSecret)
            ->get($url);

        if (!$response->successful()) {
            Log::error('M-Pesa Token Generation Failed', ['response' => $response->body()]);
            throw new Exception('Failed to obtain M-Pesa access token.');
        }

        return $response->json()['access_token'];
    }

    public function initiateStkPush(Order $order, string $phoneNumber): array
    {
        $tillNumber = SiteSetting::getValue('mpesa_till_number');
        if (!$tillNumber) {
            throw new Exception('M-Pesa Till Number is not configured in site settings.');
        }

        // Format phone number to 2547XXXXXXXX or 2541XXXXXXXX
        $formattedPhone = $this->formatPhoneNumber($phoneNumber);

        $accessToken = $this->getAccessToken();
        $url = $this->getBaseUrl() . '/mpesa/stkpush/v1/processrequest';

        // For Buy Goods (Till Number), safaricom expects:
        // TransactionType: CustomerBuyGoodsOnline
        // PartyB: Till Number (or Store Number)
        // BusinessShortCode: Store Number
        // Note: For Daraja sandbox or specific live accounts, PartyB is sometimes different, 
        // but typically BusinessShortCode = Store Number and PartyB = Store Number for CustomerBuyGoodsOnline STK Push.
        // The BusinessShortCode is the initiator shortcode (from env: MPESA_BUSINESS_SHORTCODE).
        // PartyB is the till number where funds land (from site settings).
        $shortcode = $this->businessShortcode ?: $tillNumber;
        $amountInKes = (int) ceil($order->total / 100);

        $timestamp = Carbon::now()->format('YmdHis');
        $password = base64_encode($shortcode . $this->passkey . $timestamp);

        $payload = [
            'BusinessShortCode' => $shortcode,
            'Password' => $password,
            'Timestamp' => $timestamp,
            'TransactionType' => 'CustomerBuyGoodsOnline',
            'Amount' => $amountInKes,
            'PartyA' => $formattedPhone,
            'PartyB' => $tillNumber,
            'PhoneNumber' => $formattedPhone,
            'CallBackURL' => $this->callbackUrl,
            'AccountReference' => 'Plottwear Order ' . $order->id,
            'TransactionDesc' => 'Plottwear Purchase',
        ];

        Log::info('Initiating M-Pesa STK Push', ['payload' => $payload]);

        $response = Http::withToken($accessToken)
            ->post($url, $payload);

        if (!$response->successful()) {
            Log::error('M-Pesa STK Push initiation failed', ['response' => $response->body()]);
            throw new Exception('M-Pesa payment initiation failed. Please try again.');
        }

        $resData = $response->json();
        
        if (isset($resData['ResponseCode']) && $resData['ResponseCode'] === '0') {
            $checkoutRequestId = $resData['CheckoutRequestID'];
            
            // Store checkout request ID on the order
            $order->update([
                'mpesa_transaction_id' => $checkoutRequestId
            ]);

            return [
                'success' => true,
                'checkout_request_id' => $checkoutRequestId,
                'message' => 'Payment initiated. Please check your phone for the STK push prompt.'
            ];
        }

        throw new Exception($resData['ResponseDescription'] ?? 'M-Pesa payment initiation failed.');
    }

    public function handleCallback(array $callbackData): array
    {
        Log::info('M-Pesa Callback Received', ['data' => $callbackData]);

        $stkCallback = $callbackData['Body']['stkCallback'] ?? null;
        if (!$stkCallback) {
            return ['success' => false, 'message' => 'Invalid callback body'];
        }

        $checkoutRequestId = $stkCallback['CheckoutRequestID'] ?? null;
        $resultCode = $stkCallback['ResultCode'] ?? null;
        $resultDesc = $stkCallback['ResultDesc'] ?? null;

        $order = Order::where('mpesa_transaction_id', $checkoutRequestId)->first();
        if (!$order) {
            Log::warning('M-Pesa Callback: Order not found for CheckoutRequestID', ['checkout_request_id' => $checkoutRequestId]);
            return ['success' => false, 'message' => 'Order not found for CheckoutRequestID: ' . $checkoutRequestId];
        }

        if ($resultCode == 0) {
            // Success
            $callbackMetadata = $stkCallback['CallbackMetadata']['Item'] ?? [];
            $mpesaReceiptNumber = null;

            foreach ($callbackMetadata as $item) {
                if (($item['Name'] ?? '') === 'MpesaReceiptNumber') {
                    $mpesaReceiptNumber = $item['Value'] ?? null;
                    break;
                }
            }

            // Update order status to paid and store the actual MpesaReceiptNumber
            $order->update([
                'status' => 'paid',
                'mpesa_transaction_id' => $mpesaReceiptNumber ?: $checkoutRequestId
            ]);

            Log::info("Order {$order->id} paid successfully via M-Pesa. Receipt: {$mpesaReceiptNumber}");

            return ['success' => true, 'order_id' => $order->id, 'receipt' => $mpesaReceiptNumber];
        } else {
            // Failed
            $order->update([
                'status' => 'cancelled'
            ]);

            Log::info("Order {$order->id} payment failed or cancelled by user. Description: {$resultDesc}");

            return ['success' => false, 'order_id' => $order->id, 'message' => $resultDesc];
        }
    }

    private function formatPhoneNumber(string $phoneNumber): string
    {
        // Strip any spaces, hyphens, plus signs
        $phone = preg_replace('/[^0-9]/', '', $phoneNumber);

        // If it starts with 07 or 01, replace with 2547 or 2541
        if (str_starts_with($phone, '0')) {
            $phone = '254' . substr($phone, 1);
        }

        // If it starts with +254, it has been stripped to 254, which is correct
        if (!str_starts_with($phone, '254')) {
            $phone = '254' . $phone;
        }

        return $phone;
    }
}
