<?php

use App\Models\Chama;
use App\Models\ChamaFund;
use App\Models\Contribution;
use App\Models\ContributionPlan;
use App\Models\Transaction;
use App\Models\User;
use App\Services\MpesaService;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;

function createMpesaFixture(): array
{
    $creator = User::factory()->create();

    $member = User::factory()->create([
        'phone_number' => '0712345678',
    ]);

    $chama = Chama::create([
        'name' => 'M-PESA Test Chama',
        'slug' => 'mpesa-test-chama-' . uniqid(),
        'description' => 'M-PESA integration test chama.',
        'status' => 'active',
        'currency' => 'KES',
        'created_by' => $creator->id,
    ]);

    $chama->members()->create([
        'user_id' => $creator->id,
        'role' => 'chairperson',
        'status' => 'active',
        'joined_at' => now(),
    ]);

    $chama->members()->create([
        'user_id' => $member->id,
        'role' => 'member',
        'status' => 'active',
        'joined_at' => now(),
    ]);

    $fund = ChamaFund::create([
        'chama_id' => $chama->id,
        'name' => 'Main Savings Fund',
        'slug' => 'main-savings-fund-' . uniqid(),
        'description' => 'M-PESA integration test fund.',
        'opening_balance' => 0,
        'currency' => 'KES',
        'status' => 'active',
    ]);

    $plan = ContributionPlan::create([
        'chama_id' => $chama->id,
        'chama_fund_id' => $fund->id,
        'name' => 'Monthly Savings',
        'amount' => 100,
        'frequency' => 'monthly',
        'start_date' => now()->toDateString(),
        'end_date' => null,
        'status' => 'active',
    ]);

    $contribution = Contribution::create([
        'chama_id' => $chama->id,
        'chama_fund_id' => $fund->id,
        'contribution_plan_id' => $plan->id,
        'user_id' => $member->id,
        'amount' => 100,
        'due_date' => now()->toDateString(),
        'status' => 'pending',
    ]);

    return compact(
        'creator',
        'member',
        'chama',
        'fund',
        'plan',
        'contribution'
    );
}

beforeEach(function () {
    config([
        'mpesa.environment' => 'sandbox',
        'mpesa.consumer_key' => 'test-key',
        'mpesa.consumer_secret' => 'test-secret',
        'mpesa.shortcode' => '174379',
        'mpesa.passkey' => 'test-passkey',
        'mpesa.transaction_type' => 'CustomerPayBillOnline',
        'mpesa.callback_url' => 'https://example.test/api/mpesa/stk/callback',
        'mpesa.timeout' => 30,
        'mpesa.base_urls.sandbox' => 'https://sandbox.safaricom.co.ke',
    ]);

    Cache::forget('mpesa.access_token.sandbox');
});

test('M-PESA service obtains a token and sends an STK push', function () {
    Http::fake([
        'https://sandbox.safaricom.co.ke/oauth/v1/generate*' =>
            Http::response([
                'access_token' => 'test-access-token',
            ], 200),

        'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest' =>
            Http::response([
                'MerchantRequestID' => 'merchant-request-123',
                'CheckoutRequestID' => 'ws_CO_test_123',
                'ResponseCode' => '0',
                'ResponseDescription' => 'Success. Request accepted for processing',
                'CustomerMessage' => 'Success. Request accepted for processing',
            ], 200),
    ]);

    $response = app(MpesaService::class)->stkPush(
        '0712345678',
        100,
        'CH1'
    );

    expect($response['CheckoutRequestID'])
        ->toBe('ws_CO_test_123');

    Http::assertSent(function (Request $request) {
        return str_contains(
            $request->url(),
            '/mpesa/stkpush/v1/processrequest'
        )
            && $request['PartyA'] === '254708374149'
            && $request['PhoneNumber'] === '254708374149'
            && $request['Amount'] === 100;
    });
});

test('M-PESA callback marks a successful contribution paid', function () {
    $fixture = createMpesaFixture();

    $transaction = Transaction::create([
        'chama_id' => $fixture['chama']->id,
        'chama_fund_id' => $fixture['fund']->id,
        'user_id' => $fixture['member']->id,
        'contribution_id' => $fixture['contribution']->id,
        'type' => 'contribution',
        'amount' => 100,
        'currency' => 'KES',
        'status' => 'pending',
        'payment_method' => 'mpesa',
        'reference' => 'MPESA-1-TEST',
        'external_reference' => null,
        'gateway_reference' => 'ws_CO_test_123',
        'description' => 'M-PESA STK payment',
        'processed_at' => null,
    ]);

    $response = $this->postJson('/api/mpesa/stk/callback', [
        'Body' => [
            'stkCallback' => [
                'MerchantRequestID' => 'merchant-request-123',
                'CheckoutRequestID' => 'ws_CO_test_123',
                'ResultCode' => 0,
                'ResultDesc' => 'The service request is processed successfully.',
                'CallbackMetadata' => [
                    'Item' => [
                        [
                            'Name' => 'Amount',
                            'Value' => 100,
                        ],
                        [
                            'Name' => 'MpesaReceiptNumber',
                            'Value' => 'TESTRECEIPT123',
                        ],
                        [
                            'Name' => 'PhoneNumber',
                            'Value' => 254712345678,
                        ],
                    ],
                ],
            ],
        ],
    ]);

    $response
        ->assertOk()
        ->assertJson([
            'ResultCode' => 0,
            'ResultDesc' => 'Accepted',
        ]);

    expect($fixture['contribution']->fresh()->status)
        ->toBe('paid');

    expect($fixture['contribution']->fresh()->payment_reference)
        ->toBe('TESTRECEIPT123');

    expect($transaction->fresh()->status)
        ->toBe('completed');

    expect($transaction->fresh()->external_reference)
        ->toBe('TESTRECEIPT123');
});

test('M-PESA failure callback leaves the contribution pending', function () {
    $fixture = createMpesaFixture();

    $transaction = Transaction::create([
        'chama_id' => $fixture['chama']->id,
        'chama_fund_id' => $fixture['fund']->id,
        'user_id' => $fixture['member']->id,
        'contribution_id' => $fixture['contribution']->id,
        'type' => 'contribution',
        'amount' => 100,
        'currency' => 'KES',
        'status' => 'pending',
        'payment_method' => 'mpesa',
        'reference' => 'MPESA-2-TEST',
        'external_reference' => null,
        'gateway_reference' => 'ws_CO_test_failed',
        'description' => 'M-PESA STK payment',
        'processed_at' => null,
    ]);

    $this->postJson('/api/mpesa/stk/callback', [
        'Body' => [
            'stkCallback' => [
                'MerchantRequestID' => 'merchant-request-failed',
                'CheckoutRequestID' => 'ws_CO_test_failed',
                'ResultCode' => 1032,
                'ResultDesc' => 'Request cancelled by user',
            ],
        ],
    ])->assertOk();

    expect($fixture['contribution']->fresh()->status)
        ->toBe('pending');

    expect($transaction->fresh()->status)
        ->toBe('failed');
});

test('starting an M-PESA contribution creates a pending transaction and stores the checkout request id', function () {
    $fixture = createMpesaFixture();

    Http::fake([
        'https://sandbox.safaricom.co.ke/oauth/v1/generate*' =>
            Http::response([
                'access_token' => 'test-access-token',
            ], 200),

        'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest' =>
            Http::response([
                'MerchantRequestID' => 'merchant-request-456',
                'CheckoutRequestID' => 'ws_CO_test_456',
                'ResponseCode' => '0',
                'ResponseDescription' => 'Success. Request accepted for processing',
                'CustomerMessage' => 'Success. Request accepted for processing',
            ], 200),
    ]);

    $this->actingAs($fixture['member']);

    Livewire::test('contribution-pay', [
        'contribution' => $fixture['contribution'],
    ])
        ->set('payment_method', 'mpesa')
        ->call('pay')
        ->assertHasNoErrors();

    $transaction = Transaction::query()
        ->where('contribution_id', $fixture['contribution']->id)
        ->where('payment_method', 'mpesa')
        ->first();

    expect($transaction)->not->toBeNull()
        ->and($transaction->status)->toBe('pending')
        ->and($transaction->gateway_reference)->toBe('ws_CO_test_456')
        ->and($fixture['contribution']->fresh()->status)->toBe('pending');
});

test('M-PESA service returns a graceful failure when an STK query transaction does not exist', function () {
    Http::fake([
        'https://sandbox.safaricom.co.ke/oauth/v1/generate*' =>
            Http::response([
                'access_token' => 'test-access-token',
            ], 200),

        'https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query' =>
            Http::response([
                'requestId' => 'ws_CO_test_missing',
                'errorCode' => '500.001.1001',
                'errorMessage' => 'The transaction does not Exist',
            ], 500),
    ]);

    $response = app(MpesaService::class)->queryStkPush(
        'ws_CO_test_missing'
    );

    expect($response['success'])
        ->toBeFalse()
        ->and($response['http_status'])
        ->toBe(500)
        ->and($response['request_id'])
        ->toBe('ws_CO_test_missing')
        ->and($response['error_code'])
        ->toBe('500.001.1001')
        ->and($response['error_message'])
        ->toBe('The transaction does not Exist')
        ->and($response['checkout_request_id'])
        ->toBe('ws_CO_test_missing');

    Http::assertSent(function (Request $request) {
        return str_contains(
            $request->url(),
            '/mpesa/stkpushquery/v1/query'
        )
            && $request['BusinessShortCode'] === '174379'
            && $request['CheckoutRequestID'] === 'ws_CO_test_missing'
            && isset($request['Password'])
            && $request['Password'] !== ''
            && isset($request['Timestamp'])
            && $request['Timestamp'] !== '';
    });
});