<?php

use App\Enums\BillingStatus;
use App\Enums\CreditType;
use App\Models\Billing;
use App\Models\Group;
use App\Services\DuitkuService;
use Illuminate\Support\Facades\Http;

use function Pest\Laravel\actingAs;
use function Pest\Laravel\post;

beforeEach(function () {
    Group::firstOrCreate(['name' => Group::ADMIN]);
    Group::firstOrCreate(['name' => Group::MEMBERS]);

    // Konfigurasi Duitku sandbox untuk testing.
    config()->set('services.duitku.enabled', true);
    config()->set('services.duitku.merchant_code', 'D1234');
    config()->set('services.duitku.api_key', 'test-api-key');
    config()->set('services.duitku.base_url', 'https://api-sandbox.duitku.com');
});

function duitkuConfig(): void
{
    config()->set('services.duitku.enabled', true);
    config()->set('services.duitku.merchant_code', 'D1234');
    config()->set('services.duitku.api_key', 'test-api-key');
}

function callbackSignature(string $amount, string $merchantOrderId): string
{
    return md5('D1234'.$amount.$merchantOrderId.'test-api-key');
}

function makeDuitkuBilling(): Billing
{
    $member = makeMember();
    $billing = Billing::factory()->create([
        'user_id' => $member->id,
        'jumlah_credit' => 500,
        'rate' => 100,
        'total' => 50000,
        'status' => BillingStatus::Pending,
        'payment_method' => 'duitku',
        'duitku_payment_url' => 'https://api-sandbox.duitku.com/pay/ABC123',
        'duitku_reference' => 'DTR123',
    ]);

    return $billing->fresh();
}

test('request signature uses sha256(merchantCode + timestamp + apiKey)', function () {
    duitkuConfig();
    $service = app(DuitkuService::class);

    $timestamp = '1700000000000';
    $expected = hash('sha256', 'D1234'.$timestamp.'test-api-key');

    expect($service->requestSignature($timestamp))->toBe($expected);
});

test('callback signature uses merchantCode + amount + orderId + apiKey', function () {
    duitkuConfig();
    $service = app(DuitkuService::class);

    $expected = md5('D1234'.'50000'.'INV001'.'test-api-key');

    expect($service->callbackSignature('50000', 'INV001'))->toBe($expected);
});

test('creating a duitku billing calls inquiry and redirects to payment url', function () {
    duitkuConfig();
    Http::fake([
        'api-sandbox.duitku.com/*' => Http::response([
            'paymentUrl' => 'https://api-sandbox.duitku.com/pay/XYZ',
            'reference' => 'REF1',
            'statusCode' => '00',
            'statusMessage' => 'Success',
        ], 200),
    ]);

    $member = makeMember();

    $response = actingAs($member)->post(route('billings.store'), [
        'jumlah_credit' => 50000,
        'rate' => 1,
        'payment_method' => 'duitku',
    ]);

    // Inertia::location redirects to the external Duitku payment URL.
    $response->assertRedirect('https://api-sandbox.duitku.com/pay/XYZ');

    $billing = Billing::first();
    expect($billing->payment_method)->toBe('duitku')
        ->and($billing->duitku_payment_url)->toBe('https://api-sandbox.duitku.com/pay/XYZ')
        ->and($billing->duitku_reference)->toBe('REF1')
        ->and($billing->duitku_requested_at)->not->toBeNull()
        ->and($billing->status)->toBe(BillingStatus::Pending);
});

test('valid success callback marks billing paid and adds credit', function () {
    duitkuConfig();
    $billing = makeDuitkuBilling();
    $member = $billing->user;

    expect($member->credit)->toBe(0);

    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '50000',
        'merchantOrderId' => $billing->no_invoice,
        'resultCode' => '00',
        'reference' => 'REF1',
        'signature' => callbackSignature('50000', $billing->no_invoice),
    ];

    $response = post(route('payments.duitku.callback'), $payload);

    $response->assertOk();
    $billing->refresh();
    $member->refresh();

    expect($billing->status)->toBe(BillingStatus::Lunas)
        ->and($billing->paid_at)->not->toBeNull()
        ->and($billing->duitku_callback_received_at)->not->toBeNull()
        ->and($member->credit)->toBe(500);

    $history = $member->creditHistory()->first();
    expect($history)->not->toBeNull()
        ->and($history->credit)->toBe(500)
        ->and($history->jenis)->toBe(CreditType::In);
});

test('duplicate success callback does not double credit', function () {
    duitkuConfig();
    $billing = makeDuitkuBilling();

    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '50000',
        'merchantOrderId' => $billing->no_invoice,
        'resultCode' => '00',
        'reference' => 'REF1',
        'signature' => callbackSignature('50000', $billing->no_invoice),
    ];

    post(route('payments.duitku.callback'), $payload)->assertOk();
    post(route('payments.duitku.callback'), $payload)->assertOk();

    $billing->refresh();

    expect($billing->user->fresh()->credit)->toBe(500)
        ->and($billing->user->creditHistory()->count())->toBe(1);
});

test('manual confirm after callback does not double credit', function () {
    duitkuConfig();
    $billing = makeDuitkuBilling();
    $admin = makeAdmin();

    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '50000',
        'merchantOrderId' => $billing->no_invoice,
        'resultCode' => '00',
        'reference' => 'REF1',
        'signature' => callbackSignature('50000', $billing->no_invoice),
    ];

    post(route('payments.duitku.callback'), $payload)->assertOk();
    actingAs($admin)->get(route('billings.confirm', $billing))->assertRedirect(route('billings.index'));

    $billing->refresh();

    expect($billing->user->fresh()->credit)->toBe(500)
        ->and($billing->user->creditHistory()->count())->toBe(1);
});

test('invalid callback signature is rejected', function () {
    duitkuConfig();
    $billing = makeDuitkuBilling();

    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '50000',
        'merchantOrderId' => $billing->no_invoice,
        'resultCode' => '00',
        'signature' => 'invalid-signature',
    ];

    $response = post(route('payments.duitku.callback'), $payload);

    $response->assertStatus(403);

    expect($billing->fresh()->status)->toBe(BillingStatus::Pending)
        ->and($billing->user->fresh()->credit)->toBe(0);
});

test('amount mismatch is rejected', function () {
    duitkuConfig();
    $billing = makeDuitkuBilling();

    // Signature untuk amount yang salah (99999), tetap valid secara formula.
    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '99999',
        'merchantOrderId' => $billing->no_invoice,
        'resultCode' => '00',
        'signature' => callbackSignature('99999', $billing->no_invoice),
    ];

    $response = post(route('payments.duitku.callback'), $payload);

    $response->assertStatus(422);

    expect($billing->fresh()->status)->toBe(BillingStatus::Pending)
        ->and($billing->user->fresh()->credit)->toBe(0);
});

test('non-success resultCode does not mark paid', function () {
    duitkuConfig();
    $billing = makeDuitkuBilling();

    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '50000',
        'merchantOrderId' => $billing->no_invoice,
        'resultCode' => '01',
        'signature' => callbackSignature('50000', $billing->no_invoice),
    ];

    $response = post(route('payments.duitku.callback'), $payload);

    $response->assertOk();

    expect($billing->fresh()->status)->toBe(BillingStatus::Pending)
        ->and($billing->user->fresh()->credit)->toBe(0);
});

test('unknown invoice returns 404', function () {
    duitkuConfig();

    $payload = [
        'merchantCode' => 'D1234',
        'amount' => '50000',
        'merchantOrderId' => 'NOT-EXIST',
        'resultCode' => '00',
        'signature' => callbackSignature('50000', 'NOT-EXIST'),
    ];

    post(route('payments.duitku.callback'), $payload)->assertStatus(404);
});
