<?php

namespace Tests\Unit\Services;

use App\Exceptions\CartException;
use App\Models\Cart;
use App\Models\Coupon;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Setting;
use App\Services\CartService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class CartServiceTest extends TestCase
{
    use RefreshDatabase;

    private CartService $cartService;

    protected function setUp(): void
    {
        parent::setUp();
        $this->cartService = app(CartService::class);
    }

    public function test_adding_an_item_creates_a_cart_for_the_authenticated_user(): void
    {
        $user = $this->customer();
        $this->actingAs($user);
        $variant = ProductVariant::factory()->withStock(10)->create();

        $this->cartService->addItem($variant->product, $variant, 2);

        $cart = Cart::where('user_id', $user->id)->first();
        $this->assertNotNull($cart);
        $this->assertSame(2, $cart->items->first()->quantity);
    }

    public function test_adding_the_same_variant_twice_merges_quantity_into_one_row(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(10)->create();

        $this->cartService->addItem($variant->product, $variant, 2);
        $this->cartService->addItem($variant->product, $variant, 3);

        $cart = Cart::first();
        $this->assertCount(1, $cart->items);
        $this->assertSame(5, $cart->items->first()->quantity);
    }

    public function test_cannot_add_more_than_available_stock(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(3)->create();

        $this->expectException(CartException::class);
        $this->cartService->addItem($variant->product, $variant, 5);
    }

    public function test_update_quantity_respects_stock_ceiling(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(5)->create();
        $item = $this->cartService->addItem($variant->product, $variant, 2);

        $this->expectException(CartException::class);
        $this->cartService->updateQuantity($item, 10);
    }

    public function test_remove_item_deletes_the_cart_item(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(5)->create();
        $item = $this->cartService->addItem($variant->product, $variant, 1);

        $this->cartService->removeItem($item);

        $this->assertDatabaseMissing('cart_items', ['id' => $item->id]);
    }

    public function test_apply_coupon_rejects_expired_or_invalid_codes(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(5)->create();
        $this->cartService->addItem($variant->product, $variant, 1);
        $cart = Cart::first();

        $this->expectException(CartException::class);
        $this->cartService->applyCoupon($cart, 'DOES-NOT-EXIST');
    }

    public function test_apply_coupon_rejects_orders_below_minimum(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(5)->create(); // product factory default ~1500-12000
        $variant->product->update(['original_price' => 500]);
        $this->cartService->addItem($variant->product, $variant, 1);
        $cart = Cart::first();
        Coupon::factory()->create(['code' => 'BIGSPEND', 'min_order_amount' => 10000]);

        $this->expectException(CartException::class);
        $this->cartService->applyCoupon($cart, 'BIGSPEND');
    }

    public function test_apply_and_remove_coupon(): void
    {
        $this->actingAs($this->customer());
        $variant = ProductVariant::factory()->withStock(5)->create();
        $this->cartService->addItem($variant->product, $variant, 1);
        $cart = Cart::first();
        Coupon::factory()->create(['code' => 'FIVE']);

        $this->cartService->applyCoupon($cart, 'five'); // case-insensitive
        $this->assertNotNull($cart->fresh()->coupon_id);

        $this->cartService->removeCoupon($cart);
        $this->assertNull($cart->fresh()->coupon_id);
    }

    public function test_totals_computes_free_shipping_above_threshold(): void
    {
        Setting::set('shipping.free_shipping_threshold', 5000);
        Setting::set('shipping.flat_rate', 250);

        $this->actingAs($this->customer());
        $product = Product::factory()->create(['original_price' => 3000, 'sale_price' => null]);
        $variant = ProductVariant::factory()->for($product)->withStock(5)->create();
        $this->cartService->addItem($product, $variant, 2); // 6000 subtotal

        $totals = $this->cartService->totals(Cart::first());

        $this->assertSame(6000.0, $totals['subtotal']);
        $this->assertSame(0.0, $totals['shipping']);
        $this->assertSame(6000.0, $totals['grand_total']);
    }

    public function test_totals_applies_flat_shipping_below_threshold(): void
    {
        Setting::set('shipping.free_shipping_threshold', 5000);
        Setting::set('shipping.flat_rate', 250);

        $this->actingAs($this->customer());
        $product = Product::factory()->create(['original_price' => 1000, 'sale_price' => null]);
        $variant = ProductVariant::factory()->for($product)->withStock(5)->create();
        $this->cartService->addItem($product, $variant, 1);

        $totals = $this->cartService->totals(Cart::first());

        $this->assertSame(250.0, $totals['shipping']);
        $this->assertSame(1250.0, $totals['grand_total']);
    }
}
