<?php

namespace Tests\Feature;

use App\Models\User;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\ReturnRequest;
use App\Models\ReturnRequestItem;
use App\Models\StockAdjustment;
use App\Services\StockAdjustmentService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use Livewire\Livewire;

class InventorySystemTest extends TestCase
{
    use RefreshDatabase;

    private User $admin;
    private $category;
    private Product $product;
    private ProductVariant $variant;

    protected function setUp(): void
    {
        parent::setUp();

        // Seed admin user
        $this->admin = User::create([
            'name' => 'Admin User',
            'email' => 'admin@sawdabazar.com',
            'password' => bcrypt('password123'),
            'role' => 'admin',
        ]);

        // Seed divisions, districts, upazilas for checkout
        \DB::table('bd_divisions')->insert(['id' => 1, 'name' => 'Dhaka', 'created_at' => now(), 'updated_at' => now()]);
        \DB::table('bd_districts')->insert(['id' => 1, 'division_id' => 1, 'name' => 'Dhaka', 'created_at' => now(), 'updated_at' => now()]);
        \DB::table('bd_upazilas')->insert(['id' => 1, 'district_id' => 1, 'name' => 'Mirpur', 'created_at' => now(), 'updated_at' => now()]);

        // Seed shipping method
        \DB::table('shipping_methods')->insert([
            'name' => 'Standard',
            'zone' => 'inside_dhaka',
            'cost' => 80.00,
            'is_active' => true,
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $this->category = \App\Models\Category::create([
            'name' => 'Electronics',
            'slug' => 'electronics',
            'is_visible' => true,
        ]);

        // Create product with stock 50, safety threshold 10
        $this->product = Product::create([
            'name' => 'Smart Watch',
            'slug' => 'smart-watch',
            'sku' => 'SW-001',
            'price' => 2000.00,
            'compare_at_price' => 2500.00,
            'cost_price' => 1500.00,
            'quantity' => 50,
            'security_stock' => 10,
            'published_at' => now(),
            'category_id' => $this->category->id,
        ]);

        // Create variant with stock 20, safety threshold 5
        $this->variant = ProductVariant::create([
            'product_id' => $this->product->id,
            'sku' => 'SW-001-BLACK',
            'price' => 2000.00,
            'quantity' => 20,
            'security_stock' => 5,
            'options' => ['Color' => 'Black'],
        ]);
    }

    /**
     * Test placing an order decrements stock and logs adjustments.
     */
    public function test_checkout_placement_deducts_inventory_and_logs(): void
    {
        // Add item to cart manager
        \App\Services\CartManager::addToCart($this->product->id, $this->variant->id, 2);

        Livewire::test('storefront.checkout')
            ->set('customerName', 'Test Customer')
            ->set('customerPhone', '01712345678')
            ->set('customerEmail', 'customer@example.com')
            ->set('selectedDivisionId', 1)
            ->set('selectedDistrictId', 1)
            ->set('selectedUpazilaId', 1)
            ->set('streetAddress', 'House 12, Road 4')
            ->set('paymentMethod', 'cod')
            ->call('placeOrder');

        // Variant stock should go from 20 to 18
        $this->assertEquals(18, $this->variant->fresh()->quantity);
        // Product stock should go from 50 to 48
        $this->assertEquals(48, $this->product->fresh()->quantity);

        // Verify stock adjustments logs exist
        $this->assertDatabaseHas('stock_adjustments', [
            'product_id' => $this->product->id,
            'product_variant_id' => $this->variant->id,
            'change_amount' => -2,
            'old_quantity' => 20,
            'new_quantity' => 18,
            'event' => 'order_placed',
        ]);

        $this->assertDatabaseHas('stock_adjustments', [
            'product_id' => $this->product->id,
            'product_variant_id' => null,
            'change_amount' => -2,
            'old_quantity' => 50,
            'new_quantity' => 48,
            'event' => 'order_placed',
        ]);
    }

    /**
     * Test cancelling an order increments/restores stock and logs it.
     */
    public function test_order_cancellation_restores_inventory_and_logs(): void
    {
        $order = Order::create([
            'order_number' => 'ORD-TEST101',
            'customer_name' => 'John Doe',
            'customer_phone' => '01711111111',
            'shipping_address' => 'Dhaka',
            'billing_address' => 'Dhaka',
            'subtotal' => 4000.00,
            'shipping_cost' => 80.00,
            'grand_total' => 4080.00,
            'payment_method' => 'cod',
            'payment_status' => 'pending',
            'status' => 'pending',
        ]);

        OrderItem::create([
            'order_id' => $order->id,
            'product_id' => $this->product->id,
            'product_variant_id' => $this->variant->id,
            'product_name' => 'Smart Watch',
            'product_sku' => 'SW-001-BLACK',
            'price' => 2000.00,
            'quantity' => 2,
        ]);

        // Manually trigger status change to cancelled
        $order->update(['status' => 'cancelled']);

        // Variant stock should go from 20 to 22 (restored)
        $this->assertEquals(22, $this->variant->fresh()->quantity);
        // Product stock should go from 50 to 52
        $this->assertEquals(52, $this->product->fresh()->quantity);

        $this->assertDatabaseHas('stock_adjustments', [
            'order_id' => $order->id,
            'product_variant_id' => $this->variant->id,
            'change_amount' => 2,
            'event' => 'order_cancelled',
        ]);

        // Toggling status again shouldn't double-restore stock
        $order->update(['status' => 'cancelled']);
        $this->assertEquals(22, $this->variant->fresh()->quantity);
    }

    /**
     * Test return request receipt restores stock.
     */
    public function test_return_request_receipt_restores_inventory_and_logs(): void
    {
        $order = Order::create([
            'order_number' => 'ORD-TEST102',
            'customer_name' => 'John Doe',
            'customer_phone' => '01711111111',
            'shipping_address' => 'Dhaka',
            'billing_address' => 'Dhaka',
            'subtotal' => 2000.00,
            'shipping_cost' => 80.00,
            'grand_total' => 2080.00,
            'payment_method' => 'cod',
            'payment_status' => 'pending',
            'status' => 'delivered', // Must be delivered for returns
        ]);

        $orderItem = OrderItem::create([
            'order_id' => $order->id,
            'product_id' => $this->product->id,
            'product_variant_id' => $this->variant->id,
            'product_name' => 'Smart Watch',
            'product_sku' => 'SW-001-BLACK',
            'price' => 2000.00,
            'quantity' => 2,
        ]);

        $returnRequest = ReturnRequest::create([
            'order_id' => $order->id,
            'status' => 'pending',
            'type' => 'return',
            'reason' => 'size_issue',
            'refund_amount' => 2000.00,
        ]);

        $returnItem = ReturnRequestItem::create([
            'return_request_id' => $returnRequest->id,
            'order_item_id' => $orderItem->id,
            'quantity' => 1, // Returning 1 item
            'price' => 2000.00,
        ]);

        // Transition to processing (received returned items)
        $returnRequest->update(['status' => 'processing']);

        // Variant stock should go from 20 to 21
        $this->assertEquals(21, $this->variant->fresh()->quantity);

        $this->assertDatabaseHas('stock_adjustments', [
            'return_request_id' => $returnRequest->id,
            'product_variant_id' => $this->variant->id,
            'change_amount' => 1,
            'event' => 'return_received',
        ]);

        // Transition order status to returned (simulating complete/refund action)
        $order->update(['status' => 'returned']);

        // Stock should still be 21 (no double restoration)
        $this->assertEquals(21, $this->variant->fresh()->quantity);
    }

    /**
     * Test manual adjustments log automatically via observers.
     */
    public function test_manual_database_modifications_generate_manual_logs(): void
    {
        // Directly update product via Eloquent
        $this->product->update(['quantity' => 100]);

        $this->assertDatabaseHas('stock_adjustments', [
            'product_id' => $this->product->id,
            'product_variant_id' => null,
            'change_amount' => 50,
            'old_quantity' => 50,
            'new_quantity' => 100,
            'event' => 'manual',
        ]);

        // Directly update variant
        $this->variant->update(['quantity' => 10]);

        $this->assertDatabaseHas('stock_adjustments', [
            'product_id' => $this->product->id,
            'product_variant_id' => $this->variant->id,
            'change_amount' => -10,
            'old_quantity' => 20,
            'new_quantity' => 10,
            'event' => 'manual',
        ]);
    }

    /**
     * Test low stock notifications trigger when threshold is crossed.
     */
    public function test_low_stock_notification_is_dispatched(): void
    {
        $this->actingAs($this->admin);

        // Clear existing notifications
        $this->admin->notifications()->delete();

        // Adjust variant stock from 20 to 4 (threshold is 5)
        StockAdjustmentService::adjust($this->variant, -16, 'manual');

        // Verify a database notification exists for the admin
        $this->assertEquals(1, $this->admin->unreadNotifications()->count());
        $notification = $this->admin->unreadNotifications()->first();
        $this->assertStringContainsString('SW-001-BLACK', $notification->data['body']);
        $this->assertStringContainsString(' safety threshold', $notification->data['body']);
    }

    /**
     * Test database notifications are sent when a return/refund request is created and completed.
     */
    public function test_return_refund_notifications_are_dispatched(): void
    {
        $this->actingAs($this->admin);

        // Clear existing notifications
        $this->admin->notifications()->delete();

        // Create an order
        $order = Order::create([
            'order_number' => 'ORD-TEST999',
            'customer_name' => 'Jane Doe',
            'customer_phone' => '01799999999',
            'shipping_address' => 'Dhaka',
            'subtotal' => 2000.00,
            'shipping_cost' => 80.00,
            'grand_total' => 2080.00,
            'payment_method' => 'cod',
            'status' => 'delivered',
        ]);

        $orderItem = OrderItem::create([
            'order_id' => $order->id,
            'product_id' => $this->product->id,
            'product_variant_id' => $this->variant->id,
            'product_name' => 'Smart Watch',
            'product_sku' => 'SW-001-BLACK',
            'price' => 2000.00,
            'quantity' => 1,
        ]);

        // 1. Submit Return Request
        $returnRequest = ReturnRequest::create([
            'order_id' => $order->id,
            'user_id' => $this->admin->id,
            'status' => 'pending',
            'type' => 'refund',
            'reason' => 'wrong_item',
            'refund_details' => 'bKash: 01799999999',
        ]);

        // Assert notification for return request creation is created
        $this->assertEquals(1, $this->admin->unreadNotifications()->count());
        $notification = $this->admin->unreadNotifications()->first();
        $this->assertEquals('New Refund Request', $notification->data['title']);
        $this->assertStringContainsString('wrong item', $notification->data['body']);

        // 2. Process return request status to processing
        $returnRequest->update(['status' => 'processing']);

        // 3. Complete the request
        $this->admin->notifications()->delete();

        Livewire::test(\App\Filament\Resources\ReturnRequestResource\Pages\EditReturnRequest::class, [
            'record' => $returnRequest->id,
        ])
        ->callAction('complete', data: [
            'refund_amount' => 2000.00,
            'refund_method' => 'bkash',
        ]);

        // Assert notification for refund completed is created
        $this->assertEquals(1, $this->admin->unreadNotifications()->count());
        $notification = $this->admin->unreadNotifications()->first();
        $this->assertEquals('Refund Completed', $notification->data['title']);
        $this->assertStringContainsString('Refund of BDT 2,000.00 via Bkash completed', $notification->data['body']);
    }

    /**
     * Test order item observer handles manual additions, updates, and deletions.
     */
    public function test_order_item_observer_syncs_stock(): void
    {
        \App\Observers\OrderItemObserver::$enableInConsole = true;

        try {
            $order = Order::create([
                'order_number' => 'ORD-MANUAL-101',
                'customer_name' => 'Jane Doe',
                'customer_phone' => '01799999999',
                'shipping_address' => 'Dhaka',
                'subtotal' => 2000.00,
                'shipping_cost' => 80.00,
                'grand_total' => 2080.00,
                'payment_method' => 'cod',
                'status' => 'pending',
            ]);

            // 1. Manually add an item
            // Initial variant stock is 20, product stock is 50
            $item = OrderItem::create([
                'order_id' => $order->id,
                'product_id' => $this->product->id,
                'product_variant_id' => $this->variant->id,
                'product_name' => 'Smart Watch',
                'product_sku' => 'SW-001-BLACK',
                'price' => 2000.00,
                'quantity' => 2,
            ]);

            // Stock should be decremented: variant 20 -> 18, product 50 -> 48
            $this->assertEquals(18, $this->variant->fresh()->quantity);
            $this->assertEquals(48, $this->product->fresh()->quantity);

            // 2. Update item quantity (from 2 to 3)
            $item->update(['quantity' => 3]);

            // Stock should be further decremented: variant 18 -> 17, product 48 -> 47
            $this->assertEquals(17, $this->variant->fresh()->quantity);
            $this->assertEquals(47, $this->product->fresh()->quantity);

            // 3. Update item quantity (from 3 to 1)
            $item->update(['quantity' => 1]);

            // Stock should be restored: variant 17 -> 19, product 47 -> 49
            $this->assertEquals(19, $this->variant->fresh()->quantity);
            $this->assertEquals(49, $this->product->fresh()->quantity);

            // 4. Delete the item
            $item->delete();

            // Stock should be completely restored: variant 19 -> 20, product 49 -> 50
            $this->assertEquals(20, $this->variant->fresh()->quantity);
            $this->assertEquals(50, $this->product->fresh()->quantity);
        } finally {
            \App\Observers\OrderItemObserver::$enableInConsole = false;
        }
    }
}
