<?php

declare(strict_types=1);

use App\Models\AccountCredit;
use App\Models\CreditNote;
use App\Models\DebitNote;
use App\Models\Invoice;
use App\Models\PaymentTransaction;
use App\Models\User;
use App\Services\Billing\CreditService;
use Database\Seeders\RoleAndPermissionSeeder;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);

    $this->adminUrl = 'http://'.config('app.domains.admin');
    $this->accountUrl = 'http://'.config('app.domains.account');

    $this->admin = User::factory()->admin()->create(['credit_balance' => 0]);
    $this->customer = User::factory()->customer()->create(['credit_balance' => 0]);
});

// --- CreditService Tests ---

test('issue credit note creates credit note and adds to balance', function (): void {
    $service = app(CreditService::class);

    $creditNote = $service->issueCreditNote(
        user: $this->customer,
        amount: 25.50,
        reason: 'Test credit',
        creator: $this->admin,
    );

    expect($creditNote)->toBeInstanceOf(CreditNote::class)
        ->and($creditNote->status)->toBe('issued')
        ->and((float) $creditNote->amount)->toBe(25.50)
        ->and($creditNote->number)->toStartWith('CN-');

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(25.50);

    expect(AccountCredit::where('user_id', $this->customer->id)->count())->toBe(1);
});

test('issue debit note deducts from balance down to zero', function (): void {
    $this->customer->update(['credit_balance' => 10.00]);

    $service = app(CreditService::class);

    $debitNote = $service->issueDebitNote(
        user: $this->customer,
        amount: 25.00,
        reasonType: 'adjustment',
        reason: 'Test debit',
        creator: $this->admin,
    );

    expect($debitNote)->toBeInstanceOf(DebitNote::class)
        ->and($debitNote->status)->toBe('issued')
        ->and((float) $debitNote->amount)->toBe(25.00)
        ->and($debitNote->number)->toStartWith('DN-');

    $this->customer->refresh();
    // Should only deduct 10.00 (available balance), not go below 0
    expect((float) $this->customer->credit_balance)->toBe(0.00);
});

test('adjust balance adds positive amount', function (): void {
    $service = app(CreditService::class);

    $credit = $service->adjustBalance(
        user: $this->customer,
        amount: 50.00,
        description: 'Manual top-up',
        creator: $this->admin,
    );

    expect($credit)->toBeInstanceOf(AccountCredit::class)
        ->and($credit->type)->toBe('admin_adjustment');

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(50.00);
});

test('adjust balance deducts negative amount', function (): void {
    $this->customer->update(['credit_balance' => 100.00]);

    $service = app(CreditService::class);

    $credit = $service->adjustBalance(
        user: $this->customer,
        amount: -30.00,
        description: 'Manual deduction',
        creator: $this->admin,
    );

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(70.00);
});

test('apply credits to invoice creates payment transaction', function (): void {
    $this->customer->update(['credit_balance' => 100.00]);

    $invoice = Invoice::factory()->create([
        'user_id' => $this->customer->id,
        'total' => 40.00,
        'status' => 'pending',
    ]);

    $service = app(CreditService::class);
    $applied = $service->applyCreditsToInvoice($this->customer, $invoice);

    expect($applied)->toBe(40.00);

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(60.00);

    $invoice->refresh();
    expect($invoice->status)->toBe('paid');

    $txn = PaymentTransaction::where('invoice_id', $invoice->id)
        ->where('gateway', 'credit')
        ->first();
    expect($txn)->not->toBeNull()
        ->and((float) $txn->amount)->toBe(40.00);
});

test('apply credits partial when balance is less than invoice', function (): void {
    $this->customer->update(['credit_balance' => 15.00]);

    $invoice = Invoice::factory()->create([
        'user_id' => $this->customer->id,
        'total' => 40.00,
        'status' => 'pending',
    ]);

    $service = app(CreditService::class);
    $applied = $service->applyCreditsToInvoice($this->customer, $invoice);

    expect($applied)->toBe(15.00);

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(0.00);

    $invoice->refresh();
    // Should NOT be marked as paid since credit didn't cover full amount
    expect($invoice->status)->toBe('pending');
});

test('refund to credit adds to balance', function (): void {
    $txn = PaymentTransaction::create([
        'user_id' => $this->customer->id,
        'gateway' => 'stripe',
        'amount' => 50.00,
        'currency' => 'USD',
        'status' => 'succeeded',
    ]);

    $service = app(CreditService::class);
    $credit = $service->refundToCredit($this->customer, $txn, 50.00);

    expect($credit->type)->toBe('refund')
        ->and((float) $credit->amount)->toBe(50.00);

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(50.00);
});

test('void credit note reverses balance', function (): void {
    $service = app(CreditService::class);

    $creditNote = $service->issueCreditNote(
        user: $this->customer,
        amount: 30.00,
        reason: 'Test',
        creator: $this->admin,
    );

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(30.00);

    $service->voidCreditNote($creditNote);

    $creditNote->refresh();
    expect($creditNote->status)->toBe('voided');

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(0.00);
});

test('void debit note restores balance', function (): void {
    $this->customer->update(['credit_balance' => 50.00]);

    $service = app(CreditService::class);

    $debitNote = $service->issueDebitNote(
        user: $this->customer,
        amount: 20.00,
        reasonType: 'late_fee',
        creator: $this->admin,
    );

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(30.00);

    $service->voidDebitNote($debitNote);

    $debitNote->refresh();
    expect($debitNote->status)->toBe('voided');

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(50.00);
});

// --- Admin Credit Note Controller Tests ---

test('admin can list credit notes', function (): void {
    CreditNote::factory()->count(3)->create();

    $response = $this->actingAs($this->admin)
        ->get("{$this->adminUrl}/credit-notes");

    $response->assertStatus(200);
});

test('admin can view create credit note page', function (): void {
    $response = $this->actingAs($this->admin)
        ->get("{$this->adminUrl}/credit-notes/create");

    $response->assertStatus(200);
});

test('admin can store credit note', function (): void {
    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/credit-notes", [
            'user_id' => $this->customer->id,
            'amount' => 25.00,
            'reason' => 'Service credit',
        ]);

    $response->assertRedirect();

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(25.00);

    expect(CreditNote::where('user_id', $this->customer->id)->count())->toBe(1);
});

test('admin can view credit note detail', function (): void {
    $creditNote = CreditNote::factory()->create(['user_id' => $this->customer->id]);

    $response = $this->actingAs($this->admin)
        ->get("{$this->adminUrl}/credit-notes/{$creditNote->id}");

    $response->assertStatus(200);
});

test('admin can void credit note', function (): void {
    $service = app(CreditService::class);
    $creditNote = $service->issueCreditNote(
        user: $this->customer,
        amount: 20.00,
        reason: 'Test',
        creator: $this->admin,
    );

    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/credit-notes/{$creditNote->id}/void");

    $response->assertRedirect();

    $creditNote->refresh();
    expect($creditNote->status)->toBe('voided');
});

test('admin can download credit note pdf', function (): void {
    $creditNote = CreditNote::factory()->create(['user_id' => $this->customer->id]);

    $response = $this->actingAs($this->admin)
        ->get("{$this->adminUrl}/credit-notes/{$creditNote->id}/download");

    $response->assertStatus(200);
    $response->assertHeader('content-type', 'application/pdf');
});

// --- Admin Debit Note Controller Tests ---

test('admin can list debit notes', function (): void {
    DebitNote::factory()->count(3)->create();

    $response = $this->actingAs($this->admin)
        ->get("{$this->adminUrl}/debit-notes");

    $response->assertStatus(200);
});

test('admin can store debit note', function (): void {
    $this->customer->update(['credit_balance' => 50.00]);

    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/debit-notes", [
            'user_id' => $this->customer->id,
            'amount' => 15.00,
            'reason_type' => 'late_fee',
            'reason' => 'Late payment fee',
        ]);

    $response->assertRedirect();

    $this->customer->refresh();
    expect((float) $this->customer->credit_balance)->toBe(35.00);

    expect(DebitNote::where('user_id', $this->customer->id)->count())->toBe(1);
});

test('admin can void debit note', function (): void {
    $this->customer->update(['credit_balance' => 50.00]);

    $service = app(CreditService::class);
    $debitNote = $service->issueDebitNote(
        user: $this->customer,
        amount: 20.00,
        reasonType: 'adjustment',
        creator: $this->admin,
    );

    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/debit-notes/{$debitNote->id}/void");

    $response->assertRedirect();

    $debitNote->refresh();
    expect($debitNote->status)->toBe('voided');
});

test('admin can download debit note pdf', function (): void {
    $debitNote = DebitNote::factory()->create(['user_id' => $this->customer->id]);

    $response = $this->actingAs($this->admin)
        ->get("{$this->adminUrl}/debit-notes/{$debitNote->id}/download");

    $response->assertStatus(200);
    $response->assertHeader('content-type', 'application/pdf');
});

// --- Customer Credits Page Test ---

test('customer can view credit history', function (): void {
    AccountCredit::factory()->count(3)->create(['user_id' => $this->customer->id]);

    $response = $this->actingAs($this->customer)
        ->get("{$this->accountUrl}/billing/credits");

    $response->assertStatus(200);
});

// --- Validation Tests ---

test('store credit note validates required fields', function (): void {
    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/credit-notes", []);

    $response->assertSessionHasErrors(['user_id', 'amount']);
});

test('store debit note validates required fields', function (): void {
    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/debit-notes", []);

    $response->assertSessionHasErrors(['user_id', 'amount', 'reason_type']);
});

test('store debit note validates reason type', function (): void {
    $response = $this->actingAs($this->admin)
        ->post("{$this->adminUrl}/debit-notes", [
            'user_id' => $this->customer->id,
            'amount' => 10.00,
            'reason_type' => 'invalid_type',
        ]);

    $response->assertSessionHasErrors(['reason_type']);
});

// --- Model Relation Tests ---

test('user has account credits relation', function (): void {
    AccountCredit::factory()->count(2)->create(['user_id' => $this->customer->id]);

    expect($this->customer->accountCredits)->toHaveCount(2);
});

test('user has credit notes relation', function (): void {
    CreditNote::factory()->count(2)->create(['user_id' => $this->customer->id]);

    expect($this->customer->creditNotes)->toHaveCount(2);
});

test('user has debit notes relation', function (): void {
    DebitNote::factory()->count(2)->create(['user_id' => $this->customer->id]);

    expect($this->customer->debitNotes)->toHaveCount(2);
});

test('credit note generates unique number', function (): void {
    $num1 = CreditNote::generateNumber();
    $num2 = CreditNote::generateNumber();

    expect($num1)->toStartWith('CN-')
        ->and($num2)->toStartWith('CN-')
        ->and($num1)->not->toBe($num2);
});

test('debit note generates unique number', function (): void {
    $num1 = DebitNote::generateNumber();
    $num2 = DebitNote::generateNumber();

    expect($num1)->toStartWith('DN-')
        ->and($num2)->toStartWith('DN-')
        ->and($num1)->not->toBe($num2);
});
