<?php

use App\Models\Chama;
use App\Models\ChamaFund;
use App\Models\Contribution;
use App\Models\ContributionPlan;
use App\Models\MpesaSetting;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Support\Facades\Http;

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

    $chama = Chama::create([
        'name' => 'Command Reconciliation Chama',
        'slug' => 'command-reconciliation-chama-' . uniqid(),
        'description' => 'M-PESA reconciliation command test chama.',
        'created_by' => $creator->id,
        'status' => 'active',
    ]);

    $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-command-fund-' . uniqid(),
        'description' => 'Primary savings fund.',
        'opening_balance' => 0,
        'currency' => 'KES',
        'status' => 'active',
    ]);

    $plan = ContributionPlan::create([
        'chama_id' => $chama->id,
        'chama_fund_id' => $fund->id,
        'name' => 'Weekly Savings',
        'amount' => 500,
        'frequency' => 'weekly',
        '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' => 500,
        'due_date' => now()->toDateString(),
        'status' => 'pending',
    ]);

    return [
        'creator' => $creator,
        'member' => $member,
        'chama' => $chama,
        'fund' => $fund,
        'plan' => $plan,
        'contribution' => $contribution,
    ];
}

function createCommandMpesaTransaction(
    array $fixture,
    ?string $createdAt = null
): Transaction {
    $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' => 500,
        'currency' => 'KES',
        'status' => 'pending',
        'payment_method' => 'mpesa',
        'reference' => 'MPESA-CMD-' . uniqid(),
        'gateway_reference' => 'ws_CO_COMMAND_' . uniqid(),
        'description' => 'Command reconciliation test payment',
    ]);

    if ($createdAt !== null) {
        $transaction->created_at = $createdAt;
        $transaction->updated_at = $createdAt;
        $transaction->save();
    }

    return $transaction->fresh();
}

function configureCommandMpesa(): void
{
    MpesaSetting::create([
        'environment' => 'sandbox',
        'consumer_key' => 'test-consumer-key',
        'consumer_secret' => 'test-consumer-secret',
        'shortcode' => '174379',
        'passkey' => 'test-passkey',
        'transaction_type' => 'CustomerPayBillOnline',
        'callback_url' => 'http://localhost/api/mpesa/stk/callback',
        'sandbox_phone_number' => '254700000000',
        'enabled' => true,
    ]);
}

test('M-PESA reconcile command ignores recent pending payments', function () {
    $fixture = createCommandMpesaFixture();

    configureCommandMpesa();

    $transaction = createCommandMpesaTransaction(
        $fixture,
        now()->subMinutes(10)
    );

    Http::fake();

    $this->artisan('mpesa:reconcile')
        ->expectsOutput('Found 0 stale M-PESA payment(s) to reconcile.')
        ->assertExitCode(0);

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

    Http::assertNothingSent();
});

test('M-PESA reconcile command completes a stale successful payment', function () {
    $fixture = createCommandMpesaFixture();

    configureCommandMpesa();

    $transaction = createCommandMpesaTransaction(
        $fixture,
        now()->subMinutes(45)
    );

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

        'https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query' => Http::response([
            'ResponseCode' => '0',
            'ResponseDescription' => 'The service request has been accepted successfully',
            'MerchantRequestID' => '29115-34620561-1',
            'CheckoutRequestID' => $transaction->gateway_reference,
            'ResultCode' => '0',
            'ResultDesc' => 'The service request is processed successfully.',
            'CallbackMetadata' => [
                'Item' => [
                    [
                        'Name' => 'Amount',
                        'Value' => 500,
                    ],
                    [
                        'Name' => 'MpesaReceiptNumber',
                        'Value' => 'CMDSTL001',
                    ],
                    [
                        'Name' => 'TransactionDate',
                        'Value' => 20261006031832,
                    ],
                    [
                        'Name' => 'PhoneNumber',
                        'Value' => 254700000000,
                    ],
                ],
            ],
        ], 200),
    ]);

    $this->artisan('mpesa:reconcile')
        ->expectsOutput('Found 1 stale M-PESA payment(s) to reconcile.')
        ->expectsOutput("Transaction #{$transaction->id}: completed")
        ->assertExitCode(0);

    $transaction = $transaction->fresh();
    $contribution = $fixture['contribution']->fresh();

    expect($transaction->status)->toBe('completed')
        ->and($transaction->external_reference)->toBe('CMDSTL001')
        ->and($contribution->status)->toBe('paid')
        ->and($contribution->payment_method)->toBe('mpesa')
        ->and($contribution->payment_reference)->toBe('CMDSTL001');
});

test('M-PESA reconcile command keeps stale unavailable payment pending', function () {
    $fixture = createCommandMpesaFixture();

    configureCommandMpesa();

    $transaction = createCommandMpesaTransaction(
        $fixture,
        now()->subMinutes(45)
    );

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

        'https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query' => Http::response([
            'requestId' => 'test-request-id',
            'errorCode' => '500.001.1001',
            'errorMessage' => 'Unable to process your request. Please try again later.',
        ], 500),
    ]);

    $this->artisan('mpesa:reconcile')
        ->expectsOutput('Found 1 stale M-PESA payment(s) to reconcile.')
        ->expectsOutput("Transaction #{$transaction->id}: unavailable")
        ->assertExitCode(0);

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