<?php

use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceResource\Pages\CreateInvoice;
use App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\ProductService;
use App\Models\TaxRate;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

use function Pest\Livewire\livewire;

uses(RefreshDatabase::class);

it('can render the list page', function () {
    $this->actingAs(User::factory(['is_admin' => true])->create());
    livewire(ListInvoices::class)->assertSuccessful();
});

it('cannot render the list page if user isn\'t an admin', function () {
    $this->actingAs(User::factory()->create());
    livewire(ListInvoices::class)->assertForbidden();
});

it('can create an invoice', function () {
    $user = User::factory(['is_admin' => true])->create();
    $this->actingAs($user);

    $customer = Customer::factory()->create(); // Generates a customer
    $pst_rate = TaxRate::where('name', 'PST')->value('value') ?? 0;
    $gst_rate = TaxRate::where('name', 'GST')->value('value') ?? 0;
    $hst_rate = TaxRate::where('name', 'HST')->value('value') ?? 0;

    $formData = [
        'customer_id' => $customer->id,
        'date'        => now()->toDateString(),
        'due_date'    => now()->addDays(7)->toDateString(),
        'status'      => InvoiceStatus::UNPAID->value,
        'has_gst'     => true,
        'has_pst'     => true,
        'has_hst'     => false,
    ];

    $this->livewire(CreateInvoice::class)
        ->fillForm($formData)
        ->call('create')
        ->assertHasNoErrors();

    $this->assertDatabaseHas('invoices', [
        'internal_id' => 'INV400001',
        'customer_id' => $formData['customer_id'],
        'status'      => $formData['status'],
        'has_gst'     => $formData['has_gst'],
        'has_pst'     => $formData['has_pst'],
        'has_hst'     => $formData['has_hst'],
        'gst_rate'    => $gst_rate,
        'pst_rate'    => $pst_rate,
        'hst_rate'    => $hst_rate,
    ]);

    $invoice = Invoice::where('internal_id', 'INV400001')->firstOrFail();

    $this->assertEquals($invoice->orders->isEmpty(), true);
});

it('can add orders to an invoice', function () {
    $customer = Customer::factory()->create();
    $invoice  = Invoice::factory()->create(['customer_id' => $customer->id]);
    $orders   = Order::factory()->for($customer)->count(3)->create();

    $invoice->orders()->saveMany($orders);

    $this->assertEquals($invoice->orders->count(), 3);
});

it('correctly calculates tax amounts', function () {
    /** Setup **/
    $customer = Customer::factory()->create();
    $order    = Order::factory()
        ->for($customer)
        ->has(ProductService::factory()->count(3))
        ->create();

    $invoice = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $order->invoice()->associate($invoice)->save();

    /** Action **/
    $subtotal   = $order->total_service_price;
    $pst_amount = $subtotal * $invoice->pst_rate / 100;
    $gst_amount = $subtotal * $invoice->gst_rate / 100;
    $total      = $subtotal + $pst_amount + $gst_amount;

    /** Assertions **/
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->gst_amount, $gst_amount);
    $this->assertSame($invoice->pst_amount, $pst_amount);
    $this->assertSame($invoice->total, $total);
});

it('correctly re-calculates tax amounts when a product service gets added to an order', function () {
    /** Setup **/
    $customer = Customer::factory()->create();
    $invoice  = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $order    = Order::factory()->for($customer)->create();

    $order->invoice()->associate($invoice)->save();

    ProductService::factory()->create(['order_id' => $order->id]);

    /** Action **/
    $subtotal   = $order->total_service_price;
    $pst_amount = $subtotal * $invoice->pst_rate / 100;
    $gst_amount = $subtotal * $invoice->gst_rate / 100;
    $total      = $subtotal + $pst_amount + $gst_amount;

    $invoice->refresh();

    /** Assertions **/
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->gst_amount, $gst_amount);
    $this->assertSame($invoice->pst_amount, $pst_amount);
    $this->assertSame($invoice->total, $total);
});

it('correctly re-calculates tax amounts when a product service gets removed from an order', function () {
    /** Setup **/
    $customer = Customer::factory()->create();
    $invoice  = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $order    = Order::factory()->for($customer)->create();

    $order->invoice()->associate($invoice)->save();

    $productService1 = ProductService::factory()->create(['order_id' => $order->id]);
    $productService2 = ProductService::factory()->create(['order_id' => $order->id]);

    /** Action **/
    $productService2->delete(); // Remove one product service

    $order->refresh();
    $invoice->refresh();

    $subtotal  = $order->total_service_price;
    $pstAmount = $subtotal * $invoice->pst_rate / 100;
    $gstAmount = $subtotal * $invoice->gst_rate / 100;
    $total     = $subtotal + $pstAmount + $gstAmount;

    /** Assertions **/
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->pst_amount, $pstAmount);
    $this->assertSame($invoice->gst_amount, $gstAmount);
    $this->assertSame($invoice->total, $total);
});

it('correctly re-calculates tax amounts when an order gets added', function () {
    /** Setup **/
    $customer = Customer::factory()->create();
    $order    = Order::factory()->for($customer)->create();

    ProductService::factory()->count(3)->create(['order_id' => $order->id]);

    $invoice = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $order->invoice()->associate($invoice)->save();

    /** Action **/
    $newOrder = Order::factory()->for($customer)->create();
    ProductService::factory()->count(3)->create(['order_id' => $newOrder->id]);

    $newOrder->invoice()->associate($invoice)->save();

    $subtotal   = $order->total_service_price + $newOrder->total_service_price;
    $pst_amount = $subtotal * $invoice->pst_rate / 100;
    $gst_amount = $subtotal * $invoice->gst_rate / 100;
    $total      = $subtotal + $pst_amount + $gst_amount;

    /** Assertions **/
    $this->assertSame($invoice->orders->count(), 2);
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->gst_amount, $gst_amount);
    $this->assertSame($invoice->pst_amount, $pst_amount);
    $this->assertSame($invoice->total, $total);
});

it('correctly re-calculates tax amounts when an order gets removed', function () {
    /** Setup **/
    $customer = Customer::factory()->create();
    $orders   = Order::factory()->for($customer)
        ->has(ProductService::factory()->count(2))
        ->count(2)
        ->create();

    $invoice = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $orders->each(fn (Order $order) => $order->invoice()->associate($invoice)->save());

    $orders[0]->delete();

    /** Action **/
    $subtotal   = $orders[1]->total_service_price;
    $pst_amount = $subtotal * $invoice->pst_rate / 100;
    $gst_amount = $subtotal * $invoice->gst_rate / 100;
    $total      = $subtotal + $pst_amount + $gst_amount;

    /** Assertions **/
    $this->assertSame($invoice->orders->count(), 1);
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->gst_amount, $gst_amount);
    $this->assertSame($invoice->pst_amount, $pst_amount);
    $this->assertSame($invoice->total, $total);
});

it('correctly re-calculates tax amounts when an order\'s product service\'s quantity gets changed', function () {
    $customer = Customer::factory()->create();
    $order    = Order::factory()
        ->for($customer)
        ->has(ProductService::factory()->count(3))
        ->create();

    $invoice = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $order->invoice()->associate($invoice)->save();

    $service = $invoice->orders->first()->productServices->first();
    $service->amount += 10;
    $service->save();

    $invoice->refresh();
    $order->refresh();

    /** Action **/
    $subtotal   = $order->total_service_price;
    $pst_amount = $subtotal * $invoice->pst_rate / 100;
    $gst_amount = $subtotal * $invoice->gst_rate / 100;
    $total      = $subtotal + $pst_amount + $gst_amount;

    /** Assertions **/
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->gst_amount, $gst_amount);
    $this->assertSame($invoice->pst_amount, $pst_amount);
    $this->assertSame($invoice->total, $total);
});

it('correctly re-calculates tax amounts when an order\'s product service\'s amount gets changed', function () {
    $customer = Customer::factory()->create();
    $order    = Order::factory()
        ->for($customer)
        ->has(ProductService::factory()->count(3))
        ->create();

    $invoice = Invoice::factory(['has_gst' => true, 'has_pst' => true])->create(['customer_id' => $customer->id]);
    $order->invoice()->associate($invoice)->save();

    $service = $invoice->orders->first()->productServices->first();
    $service->amount_price += 10;
    $service->save();

    $invoice->refresh();
    $order->refresh();

    /** Action **/
    $subtotal   = $order->total_service_price;
    $pst_amount = $subtotal * $invoice->pst_rate / 100;
    $gst_amount = $subtotal * $invoice->gst_rate / 100;
    $total      = $subtotal + $pst_amount + $gst_amount;

    /** Assertions **/
    $this->assertSame($invoice->subtotal, $subtotal);
    $this->assertSame($invoice->gst_amount, $gst_amount);
    $this->assertSame($invoice->pst_amount, $pst_amount);
    $this->assertSame($invoice->total, $total);
});