<?php

namespace Tests\Feature;

use App\Models\Customer;
use App\Models\CompanyProfile;
use App\Models\JournalEntry;
use App\Models\Product;
use App\Models\Supplier;
use App\Models\StockLedger;
use App\Models\User;
use App\Models\Warehouse;
use App\Models\WarehouseStock;
use App\Services\PurchaseService;
use App\Services\ReportingService;
use App\Services\ReturnService;
use App\Services\SalesService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use RuntimeException;
use Tests\TestCase;

class ErpTransactionTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
        $this->actingAs(User::where('username', 'admin')->firstOrFail());
    }

    public function test_purchase_sale_and_partial_return_keep_stock_and_journals_consistent(): void
    {
        $warehouse = Warehouse::where('code', 'WH-001')->firstOrFail();
        $product = Product::where('product_code', 'PRD-001')->firstOrFail();
        $supplier = Supplier::where('supplier_code', 'SUP-0001')->firstOrFail();
        $customer = Customer::where('customer_code', 'CASH')->firstOrFail();
        $before = (string) WarehouseStock::where('warehouse_id', $warehouse->id)->where('product_id', $product->id)->value('qty');

        $purchase = app(PurchaseService::class)->createDraft([
            'supplier_id' => $supplier->id,
            'warehouse_id' => $warehouse->id,
            'branch_id' => $warehouse->branch_id,
            'purchase_date' => '2026-08-10',
            'payment_type' => 'cash',
        ], [[
            'product_id' => $product->id,
            'unit_id' => $product->base_unit_id,
            'quantity' => 10,
            'unit_cost' => 2,
            'vat_rate' => 15,
        ]]);
        app(PurchaseService::class)->finalize($purchase);

        $sale = app(SalesService::class)->createDraft([
            'customer_id' => $customer->id,
            'warehouse_id' => $warehouse->id,
            'branch_id' => $warehouse->branch_id,
            'invoice_date' => '2026-08-11',
            'invoice_type' => 'simplified_tax_invoice',
            'payment_type' => 'cash',
            'price_mode' => 'Exclusive VAT',
        ], [[
            'product_id' => $product->id,
            'unit_id' => $product->base_unit_id,
            'quantity' => 2,
            'unit_price' => 3,
            'vat_rate' => 15,
        ]]);
        $sale = app(SalesService::class)->finalize($sale);

        $return = app(ReturnService::class)->createSalesReturn($sale, '2026-08-11', [[
            'sale_item_id' => $sale->items->first()->id,
            'quantity' => 1,
        ]], 'Damaged pack');

        $after = (string) WarehouseStock::where('warehouse_id', $warehouse->id)->where('product_id', $product->id)->value('qty');
        $this->assertSame(0, bccomp($after, bcadd($before, '9', 4), 4));
        $this->assertSame('finalized', $purchase->fresh()->status);
        $this->assertSame('finalized', $sale->fresh()->status);
        $this->assertSame('finalized', $return->status);
        $this->assertNotEmpty($sale->zatca_qr_tlv);

        $entries = JournalEntry::whereIn('reference_type', ['purchase', 'sale', 'sales_return'])
            ->whereIn('reference_id', [$purchase->id, $sale->id, $return->id])->get();
        $this->assertGreaterThanOrEqual(3, $entries->count());
        foreach ($entries as $entry) {
            $this->assertSame(0, bccomp((string) $entry->total_debit, (string) $entry->total_credit, 2));
        }

        $this->expectException(RuntimeException::class);
        app(ReturnService::class)->createSalesReturn($sale, '2026-08-11', [[
            'sale_item_id' => $sale->items->first()->id,
            'quantity' => 2,
        ]]);
    }

    public function test_invoice_numbers_are_unique_and_finalization_is_immutable(): void
    {
        $warehouse = Warehouse::where('code', 'WH-001')->firstOrFail();
        $product = Product::where('product_code', 'PRD-003')->firstOrFail();
        $customer = Customer::where('customer_code', 'CASH')->firstOrFail();
        $makeDraft = fn () => app(SalesService::class)->createDraft([
            'customer_id' => $customer->id, 'warehouse_id' => $warehouse->id, 'branch_id' => $warehouse->branch_id,
            'invoice_date' => '2026-08-11', 'invoice_type' => 'simplified_tax_invoice', 'payment_type' => 'cash', 'price_mode' => 'Exclusive VAT',
        ], [['product_id' => $product->id, 'unit_id' => $product->base_unit_id, 'quantity' => 1, 'unit_price' => 2, 'vat_rate' => 15]]);

        $first = $makeDraft();
        $second = $makeDraft();
        $this->assertNotSame($first->invoice_no, $second->invoice_no);

        app(SalesService::class)->finalize($first);
        $this->expectException(RuntimeException::class);
        app(SalesService::class)->finalize($first);
    }

    public function test_non_tax_invoice_counts_as_sales_without_vat_or_zatca_qr(): void
    {
        $warehouse = Warehouse::where('code', 'WH-001')->firstOrFail();
        $product = Product::where('product_code', 'PRD-001')->firstOrFail();
        $customer = Customer::where('customer_code', 'CASH')->firstOrFail();

        $sale = app(SalesService::class)->createDraft([
            'customer_id' => $customer->id,
            'warehouse_id' => $warehouse->id,
            'branch_id' => $warehouse->branch_id,
            'invoice_date' => '2026-08-11',
            'invoice_type' => 'non_tax_invoice',
            'payment_type' => 'cash',
            'price_mode' => 'Inclusive VAT',
        ], [[
            'product_id' => $product->id,
            'unit_id' => $product->base_unit_id,
            'quantity' => 2,
            'unit_price' => 100,
            'vat_rate' => 15,
        ]]);

        $this->assertSame('200.00', $sale->taxable_total);
        $this->assertSame('0.00', $sale->vat_total);
        $this->assertSame('200.00', $sale->grand_total);
        $this->assertSame('0.00', $sale->items->first()->vat_rate_snapshot);
        $this->assertSame('0.00', $sale->items->first()->vat_amount);

        $sale = app(SalesService::class)->finalize($sale);
        $this->assertNull($sale->zatca_qr_tlv);

        $journal = JournalEntry::with('lines.account')->where('reference_type', 'sale')->where('reference_id', $sale->id)->firstOrFail();
        $this->assertSame('200.00', $journal->lines->firstWhere('account.code', '4110')?->credit);
        $this->assertNull($journal->lines->firstWhere('account.code', '2120'));

        $reports = app(ReportingService::class);
        $salesDocuments = $reports->run('sales', '2026-08-11', '2026-08-11', null)['rows']->pluck('document')->all();
        $this->assertContains($sale->invoice_no, $salesDocuments);
        $this->assertNotContains($sale->invoice_no, $reports->run('vat-sales', '2026-08-11', '2026-08-11', null)['rows']->pluck('document')->all());

        $print = $this->get(route('sales.print', $sale));
        $print->assertOk()
            ->assertSee('>INVOICE<', false)
            ->assertDontSee('TAX INVOICE')
            ->assertDontSee(CompanyProfile::getProfile()->vat_number)
            ->assertDontSee('<svg', false);
    }

    public function test_purchase_increases_stock_and_sale_can_take_stock_below_zero(): void
    {
        $warehouse = Warehouse::where('code', 'WH-001')->firstOrFail();
        $product = Product::where('product_code', 'PRD-001')->firstOrFail();
        $supplier = Supplier::where('supplier_code', 'SUP-0001')->firstOrFail();
        $customer = Customer::where('customer_code', 'CASH')->firstOrFail();

        $product->update(['allow_negative_stock' => false]);
        WarehouseStock::updateOrCreate(
            ['warehouse_id' => $warehouse->id, 'product_id' => $product->id],
            ['qty' => 0, 'avg_cost' => 2]
        );

        $purchase = app(PurchaseService::class)->createDraft([
            'supplier_id' => $supplier->id,
            'warehouse_id' => $warehouse->id,
            'branch_id' => $warehouse->branch_id,
            'purchase_date' => '2026-08-10',
            'payment_type' => 'cash',
        ], [[
            'product_id' => $product->id,
            'unit_id' => $product->base_unit_id,
            'quantity' => 2,
            'unit_cost' => 2,
            'vat_rate' => 15,
        ]]);
        app(PurchaseService::class)->finalize($purchase);

        $this->assertDatabaseHas('warehouse_stocks', [
            'warehouse_id' => $warehouse->id,
            'product_id' => $product->id,
            'qty' => 2,
        ]);

        $sale = app(SalesService::class)->createDraft([
            'customer_id' => $customer->id,
            'warehouse_id' => $warehouse->id,
            'branch_id' => $warehouse->branch_id,
            'invoice_date' => '2026-08-11',
            'invoice_type' => 'simplified_tax_invoice',
            'payment_type' => 'cash',
            'price_mode' => 'Exclusive VAT',
        ], [[
            'product_id' => $product->id,
            'unit_id' => $product->base_unit_id,
            'quantity' => 5,
            'unit_price' => 3,
            'vat_rate' => 15,
        ]]);
        app(SalesService::class)->finalize($sale);

        $stock = WarehouseStock::where('warehouse_id', $warehouse->id)
            ->where('product_id', $product->id)
            ->firstOrFail();
        $this->assertSame(0, bccomp((string) $stock->qty, '-3.0000', 4));
        $this->assertSame(0, bccomp(
            (string) StockLedger::where('reference_type', 'sales')->where('reference_id', $sale->id)->value('running_quantity'),
            '-3.0000',
            4
        ));
    }
}
