<?php

declare(strict_types=1);

use App\Models\AuditLog;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\User;
use App\Notifications\InvoiceNotification;
use Database\Seeders\RoleAndPermissionSeeder;
use Illuminate\Support\Facades\Notification;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);
    $this->adminUrl = 'http://'.config('app.domains.admin');
    $this->admin = User::factory()->admin()->create();
});

// ---------------------------------------------------------------------------
// Create Invoice
// ---------------------------------------------------------------------------
describe('Create Invoice', function (): void {
    it('displays the create invoice page with customer list', function (): void {
        User::factory()->customer()->count(3)->create();

        $this->actingAs($this->admin)
            ->get($this->adminUrl.'/invoices/create')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Invoices/Create')
                ->has('customers')
            );
    });

    it('creates a draft invoice without sending email', function (): void {
        $customer = User::factory()->customer()->create();

        Notification::fake();

        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [
                    ['description' => 'VPS Hosting', 'quantity' => 1, 'unit_price' => '29.99'],
                    ['description' => 'Backup Service', 'quantity' => 2, 'unit_price' => '5.00'],
                ],
                'due_date' => now()->addDays(30)->format('Y-m-d'),
                'notes' => 'Test invoice notes',
                'send_immediately' => false,
            ]);

        $invoice = Invoice::query()->latest()->first();
        expect($invoice)->not->toBeNull()
            ->and($invoice->user_id)->toBe($customer->id)
            ->and($invoice->status)->toBe('draft')
            ->and($invoice->gateway)->toBe('manual')
            ->and($invoice->total)->toBe('39.99')
            ->and($invoice->notes)->toBe('Test invoice notes')
            ->and($invoice->items()->count())->toBe(2);

        $items = $invoice->items;
        expect($items[0]->description)->toBe('VPS Hosting')
            ->and($items[0]->quantity)->toBe(1)
            ->and($items[0]->amount)->toBe('29.99')
            ->and($items[1]->description)->toBe('Backup Service')
            ->and($items[1]->quantity)->toBe(2)
            ->and($items[1]->amount)->toBe('5.00');

        Notification::assertNothingSent();

        $response->assertRedirect($this->adminUrl.'/invoices/'.$invoice->id)
            ->assertSessionHas('success');
    });

    it('creates an invoice and sends email immediately', function (): void {
        $customer = User::factory()->customer()->create();

        Notification::fake();

        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [
                    ['description' => 'Dedicated Server', 'quantity' => 1, 'unit_price' => '199.99'],
                ],
                'due_date' => now()->addDays(14)->format('Y-m-d'),
                'notes' => null,
                'send_immediately' => true,
            ]);

        $invoice = Invoice::query()->latest()->first();
        expect($invoice)->not->toBeNull()
            ->and($invoice->status)->toBe('pending');

        Notification::assertSentTo($customer, InvoiceNotification::class);

        $response->assertRedirect($this->adminUrl.'/invoices/'.$invoice->id);
    });

    it('creates audit log when invoice is created', function (): void {
        $customer = User::factory()->customer()->create();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [
                    ['description' => 'Test Service', 'quantity' => 1, 'unit_price' => '10.00'],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
                'notes' => null,
                'send_immediately' => false,
            ]);

        $invoice = Invoice::query()->latest()->first();
        $auditLog = AuditLog::query()
            ->where('action', 'create_invoice')
            ->where('resource_id', $invoice->id)
            ->first();

        expect($auditLog)->not->toBeNull()
            ->and($auditLog->admin_id)->toBe($this->admin->id)
            ->and($auditLog->user_id)->toBe($customer->id)
            ->and($auditLog->resource_type)->toBe('invoice');
    });

    it('validates required fields when creating invoice', function (): void {
        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => null,
                'items' => [],
                'due_date' => '',
            ]);

        $response->assertSessionHasErrors(['customer_id', 'items', 'due_date']);
        expect(Invoice::query()->count())->toBe(0);
    });

    it('validates line items have required fields', function (): void {
        $customer = User::factory()->customer()->create();

        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [
                    ['description' => '', 'quantity' => 0, 'unit_price' => ''],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

        $response->assertSessionHasErrors([
            'items.0.description',
            'items.0.quantity',
            'items.0.unit_price',
        ]);
    });

    it('validates customer exists', function (): void {
        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => 99999,
                'items' => [
                    ['description' => 'Test', 'quantity' => 1, 'unit_price' => '10.00'],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

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

    it('validates due date is not in the past', function (): void {
        $customer = User::factory()->customer()->create();

        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [
                    ['description' => 'Test', 'quantity' => 1, 'unit_price' => '10.00'],
                ],
                'due_date' => now()->subDays(1)->format('Y-m-d'),
            ]);

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

    it('calculates total correctly with multiple line items', function (): void {
        $customer = User::factory()->customer()->create();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [
                    ['description' => 'Item 1', 'quantity' => 2, 'unit_price' => '15.50'],
                    ['description' => 'Item 2', 'quantity' => 3, 'unit_price' => '10.00'],
                    ['description' => 'Item 3', 'quantity' => 1, 'unit_price' => '5.99'],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

        $invoice = Invoice::query()->latest()->first();
        // (2 * 15.50) + (3 * 10.00) + (1 * 5.99) = 31.00 + 30.00 + 5.99 = 66.99
        expect($invoice->total)->toBe('66.99');
    });

    it('generates unique invoice number', function (): void {
        $customer = User::factory()->customer()->create();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [['description' => 'Test', 'quantity' => 1, 'unit_price' => '10.00']],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [['description' => 'Test', 'quantity' => 1, 'unit_price' => '10.00']],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

        $invoices = Invoice::query()->latest()->take(2)->get();
        expect($invoices[0]->number)->not->toBe($invoices[1]->number);
    });
});

// ---------------------------------------------------------------------------
// Edit Invoice
// ---------------------------------------------------------------------------
describe('Edit Invoice', function (): void {
    it('displays the edit invoice page for draft invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);
        InvoiceItem::factory()->count(2)->create(['invoice_id' => $invoice->id]);

        $this->actingAs($this->admin)
            ->get($this->adminUrl.'/invoices/'.$invoice->id.'/edit')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Invoices/Edit')
                ->has('invoice')
                ->has('invoice.items', 2)
            );
    });

    it('displays the edit invoice page for pending invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'pending',
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $this->actingAs($this->admin)
            ->get($this->adminUrl.'/invoices/'.$invoice->id.'/edit')
            ->assertOk();
    });

    it('redirects when trying to edit paid invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'paid',
        ]);

        $response = $this->actingAs($this->admin)
            ->get($this->adminUrl.'/invoices/'.$invoice->id.'/edit');

        $response->assertRedirect($this->adminUrl.'/invoices/'.$invoice->id)
            ->assertSessionHas('error');
    });

    it('redirects when trying to edit void invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'void',
        ]);

        $response = $this->actingAs($this->admin)
            ->get($this->adminUrl.'/invoices/'.$invoice->id.'/edit');

        $response->assertRedirect($this->adminUrl.'/invoices/'.$invoice->id)
            ->assertSessionHas('error');
    });

    it('updates invoice line items and recalculates total', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
            'total' => 100.00,
        ]);
        InvoiceItem::factory()->create([
            'invoice_id' => $invoice->id,
            'description' => 'Old Item',
            'quantity' => 1,
            'amount' => 100.00,
        ]);

        $response = $this->actingAs($this->admin)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [
                    ['description' => 'New Item 1', 'quantity' => 2, 'unit_price' => '25.00'],
                    ['description' => 'New Item 2', 'quantity' => 1, 'unit_price' => '15.99'],
                ],
                'due_date' => now()->addDays(14)->format('Y-m-d'),
                'notes' => 'Updated notes',
            ]);

        $invoice->refresh();
        expect($invoice->total)->toBe('65.99')
            ->and($invoice->notes)->toBe('Updated notes')
            ->and($invoice->items()->count())->toBe(2);

        $items = $invoice->items;
        expect($items[0]->description)->toBe('New Item 1')
            ->and($items[1]->description)->toBe('New Item 2');

        $response->assertRedirect($this->adminUrl.'/invoices/'.$invoice->id)
            ->assertSessionHas('success');
    });

    it('updates invoice due date', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'pending',
            'due_date' => now()->addDays(7),
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $newDueDate = now()->addDays(21)->format('Y-m-d');

        $this->actingAs($this->admin)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [
                    ['description' => 'Test Item', 'quantity' => 1, 'unit_price' => '10.00'],
                ],
                'due_date' => $newDueDate,
                'notes' => '',
            ]);

        $invoice->refresh();
        expect($invoice->due_date->format('Y-m-d'))->toBe($newDueDate);
    });

    it('creates audit log when invoice is updated', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $this->actingAs($this->admin)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [
                    ['description' => 'Updated Item', 'quantity' => 1, 'unit_price' => '50.00'],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
                'notes' => 'Updated',
            ]);

        $auditLog = AuditLog::query()
            ->where('action', 'update_invoice')
            ->where('resource_id', $invoice->id)
            ->first();

        expect($auditLog)->not->toBeNull()
            ->and($auditLog->admin_id)->toBe($this->admin->id)
            ->and($auditLog->user_id)->toBe($customer->id)
            ->and($auditLog->resource_type)->toBe('invoice');
    });

    it('validates line items when updating invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $response = $this->actingAs($this->admin)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [
                    ['description' => '', 'quantity' => -1, 'unit_price' => 'invalid'],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

        $response->assertSessionHasErrors([
            'items.0.description',
            'items.0.quantity',
            'items.0.unit_price',
        ]);
    });

    it('requires at least one line item when updating', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $response = $this->actingAs($this->admin)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

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

    it('prevents updating paid invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'paid',
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $response = $this->actingAs($this->admin)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [
                    ['description' => 'New Item', 'quantity' => 1, 'unit_price' => '99.99'],
                ],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ]);

        $response->assertRedirect($this->adminUrl.'/invoices/'.$invoice->id)
            ->assertSessionHas('error');

        // Invoice should not be modified
        $invoice->refresh();
        expect($invoice->items[0]->description)->not->toBe('New Item');
    });
});

// ---------------------------------------------------------------------------
// Resend Invoice Email
// ---------------------------------------------------------------------------
describe('Resend Invoice Email', function (): void {
    it('resends invoice email to customer', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'pending',
        ]);

        Notification::fake();

        $response = $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices/'.$invoice->id.'/resend');

        Notification::assertSentTo($customer, InvoiceNotification::class);

        $response->assertRedirect()
            ->assertSessionHas('success');
    });

    it('resends invoice email for draft invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);

        Notification::fake();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices/'.$invoice->id.'/resend');

        Notification::assertSentTo($customer, InvoiceNotification::class);
    });

    it('resends invoice email for overdue invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'overdue',
        ]);

        Notification::fake();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices/'.$invoice->id.'/resend');

        Notification::assertSentTo($customer, InvoiceNotification::class);
    });

    it('creates audit log when invoice is resent', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'pending',
        ]);

        Notification::fake();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices/'.$invoice->id.'/resend');

        $auditLog = AuditLog::query()
            ->where('action', 'resend_invoice')
            ->where('resource_id', $invoice->id)
            ->first();

        expect($auditLog)->not->toBeNull()
            ->and($auditLog->admin_id)->toBe($this->admin->id)
            ->and($auditLog->user_id)->toBe($customer->id)
            ->and($auditLog->resource_type)->toBe('invoice');
    });

    it('queues invoice notification when resending', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'pending',
        ]);

        Notification::fake();

        $this->actingAs($this->admin)
            ->post($this->adminUrl.'/invoices/'.$invoice->id.'/resend');

        Notification::assertSentTo(
            $customer,
            InvoiceNotification::class,
            function ($notification) use ($invoice) {
                return $notification->invoice->id === $invoice->id;
            }
        );
    });
});

// ---------------------------------------------------------------------------
// Authorization
// ---------------------------------------------------------------------------
describe('Authorization', function (): void {
    it('denies customer access to create invoice page', function (): void {
        $customer = User::factory()->customer()->create();

        $this->actingAs($customer)
            ->get($this->adminUrl.'/invoices/create')
            ->assertForbidden();
    });

    it('denies customer access to edit invoice page', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);

        $this->actingAs($customer)
            ->get($this->adminUrl.'/invoices/'.$invoice->id.'/edit')
            ->assertForbidden();
    });

    it('denies customer ability to create invoice', function (): void {
        $customer = User::factory()->customer()->create();

        $this->actingAs($customer)
            ->post($this->adminUrl.'/invoices', [
                'customer_id' => $customer->id,
                'items' => [['description' => 'Test', 'quantity' => 1, 'unit_price' => '10.00']],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ])
            ->assertForbidden();
    });

    it('denies customer ability to update invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'draft',
        ]);
        InvoiceItem::factory()->create(['invoice_id' => $invoice->id]);

        $this->actingAs($customer)
            ->put($this->adminUrl.'/invoices/'.$invoice->id, [
                'items' => [['description' => 'Test', 'quantity' => 1, 'unit_price' => '10.00']],
                'due_date' => now()->addDays(7)->format('Y-m-d'),
            ])
            ->assertForbidden();
    });

    it('denies customer ability to resend invoice', function (): void {
        $customer = User::factory()->customer()->create();
        $invoice = Invoice::factory()->create([
            'user_id' => $customer->id,
            'status' => 'pending',
        ]);

        $this->actingAs($customer)
            ->post($this->adminUrl.'/invoices/'.$invoice->id.'/resend')
            ->assertForbidden();
    });
});
