<?php
namespace Services;

use Core\Database;

class MpesaService {
    private Database $db;
    private array $school;

    public function __construct(int $schoolId) {
        $this->db     = Database::getInstance();
        $this->school = $this->db->fetch("SELECT * FROM schools WHERE id=?", [$schoolId]) ?? [];
    }

    private function getAccessToken(): ?string {
        $key    = $this->school['mpesa_consumer_key']    ?? '';
        $secret = $this->school['mpesa_consumer_secret'] ?? '';
        if (!$key || !$secret) return null;

        $credentials = base64_encode("$key:$secret");
        $ch = curl_init('https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER     => ["Authorization: Basic $credentials"],
            CURLOPT_SSL_VERIFYPEER => false,
        ]);
        $res = json_decode(curl_exec($ch), true);
        curl_close($ch);
        return $res['access_token'] ?? null;
    }

    public function stkPush(string $phone, float $amount, int $invoiceId, string $accountRef): array {
        $token    = $this->getAccessToken();
        if (!$token) return ['success'=>false, 'message'=>'M-Pesa not configured. Please set up credentials in Settings.'];

        $paybill  = $this->school['mpesa_paybill']  ?? '';
        $passkey  = $this->school['mpesa_passkey']  ?? '';
        if (!$paybill || !$passkey) return ['success'=>false, 'message'=>'Paybill or passkey not configured.'];

        $timestamp = date('YmdHis');
        $password  = base64_encode($paybill . $passkey . $timestamp);
        $phone     = preg_replace('/^0/', '254', preg_replace('/^\+/', '', $phone));
        $callbackUrl = rtrim(BASE_URL, '/') . '/mpesa/callback';

        $payload = [
            'BusinessShortCode' => $paybill,
            'Password'          => $password,
            'Timestamp'         => $timestamp,
            'TransactionType'   => 'CustomerPayBillOnline',
            'Amount'            => (int)ceil($amount),
            'PartyA'            => $phone,
            'PartyB'            => $paybill,
            'PhoneNumber'       => $phone,
            'CallBackURL'       => $callbackUrl,
            'AccountReference'  => $accountRef,
            'TransactionDesc'   => 'School Fee Payment',
        ];

        $ch = curl_init('https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => json_encode($payload),
            CURLOPT_HTTPHEADER     => [
                "Authorization: Bearer $token",
                "Content-Type: application/json",
            ],
            CURLOPT_SSL_VERIFYPEER => false,
        ]);
        $res = json_decode(curl_exec($ch), true);
        curl_close($ch);

        if (($res['ResponseCode'] ?? '') === '0') {
            // Log the pending STK push
            $this->db->insert('mpesa_stk_requests', [
                'invoice_id'       => $invoiceId,
                'checkout_request_id' => $res['CheckoutRequestID'],
                'merchant_request_id' => $res['MerchantRequestID'],
                'phone'            => $phone,
                'amount'           => (int)ceil($amount),
                'status'           => 'pending',
                'created_at'       => date('Y-m-d H:i:s'),
                'updated_at'       => date('Y-m-d H:i:s'),
            ]);
            return ['success'=>true, 'message'=>'STK Push sent! Check your phone and enter your M-Pesa PIN.', 'checkout_id'=>$res['CheckoutRequestID']];
        }

        return ['success'=>false, 'message'=>$res['errorMessage'] ?? $res['ResponseDescription'] ?? 'STK Push failed. Try again.'];
    }

    public function handleCallback(array $data): void {
        $body       = $data['Body']['stkCallback'] ?? $data;
        $checkoutId = $body['CheckoutRequestID'] ?? '';
        $resultCode = $body['ResultCode'] ?? 1;

        // Extract metadata
        $items = [];
        foreach ($body['CallbackMetadata']['Item'] ?? [] as $item)
            $items[$item['Name']] = $item['Value'] ?? '';

        $amount     = (float)($items['Amount'] ?? 0);
        $ref        = $items['MpesaReceiptNumber'] ?? '';
        $accountRef = $items['AccountReference'] ?? ''; // assessment_no
        $phone      = $items['PhoneNumber'] ?? '';

        if ($resultCode != 0) {
            // Mark STK request as failed if exists
            if ($checkoutId)
                $this->db->query("UPDATE mpesa_stk_requests SET status='failed',updated_at=? WHERE checkout_request_id=?",
                    [date('Y-m-d H:i:s'), $checkoutId]);
            return;
        }

        // Try to match via STK request first
        $req = $checkoutId ? $this->db->fetch("SELECT * FROM mpesa_stk_requests WHERE checkout_request_id=?", [$checkoutId]) : null;

        if ($req) {
            // STK Push initiated from system
            (new FeeService())->recordPayment($req['invoice_id'], $amount, 'mpesa', $ref, 0);
            $this->db->query("UPDATE mpesa_stk_requests SET status='completed',mpesa_ref=?,updated_at=? WHERE id=?",
                [$ref, date('Y-m-d H:i:s'), $req['id']]);
        } elseif ($accountRef) {
            // Manual Paybill payment — match by assessment_no
            $invoice = $this->db->fetch(
                "SELECT fi.* FROM fee_invoices fi
                 JOIN students s ON s.id=fi.student_id
                 JOIN terms t ON t.id=fi.term_id
                 WHERE s.assessment_no=? AND t.is_current=1 AND fi.status!='paid'
                 ORDER BY fi.id DESC LIMIT 1",
                [$accountRef]
            );
            if ($invoice)
                (new FeeService())->recordPayment($invoice['id'], $amount, 'mpesa', $ref, 0);
        }
    }

    public function checkStatus(string $checkoutId): array {
        $req = $this->db->fetch("SELECT * FROM mpesa_stk_requests WHERE checkout_request_id=?", [$checkoutId]);
        if (!$req) return ['status'=>'not_found'];
        return ['status'=>$req['status'], 'mpesa_ref'=>$req['mpesa_ref'] ?? null];
    }
}
