<?php

namespace Tests\Feature;

use App\Models\BuyQueue;
use App\Models\Item;
use App\Models\User;
use App\Notifications\BuyerLeftQueue;
use App\Notifications\PromotedToPrimaryBuyer;
use App\Notifications\PurchaseConfirmed;
use App\Notifications\RemovedFromQueue;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

class BuyQueueTest extends TestCase
{
    use RefreshDatabase;

    private function user(): User
    {
        return User::factory()->create()->refresh();
    }

    private function item(User $seller, string $status = 'active'): Item
    {
        return Item::create([
            'user_id' => $seller->id,
            'title' => 'Test Synth',
            'description' => 'A synth',
            'price' => 100,
            'condition' => 'Good',
            'quantity' => 1,
            'listing_type' => 'sale',
            'status' => $status,
            'location_country' => 'US',
        ]);
    }

    private function joinQueue(Item $item, User $user, int $position): BuyQueue
    {
        return BuyQueue::create([
            'item_id' => $item->id,
            'user_id' => $user->id,
            'position' => $position,
            'status' => 'pending',
        ]);
    }

    public function test_cannot_buy_a_sold_item(): void
    {
        $item = $this->item($this->user(), 'sold');
        $buyer = $this->user();

        $response = $this->actingAs($buyer)->post(route('items.buy', $item));

        $response->assertRedirect(route('items.show', $item));
        $response->assertSessionHas('error', 'This item is no longer available.');
        $this->assertDatabaseCount('buy_queue', 0);
    }

    public function test_checkout_page_redirects_for_unavailable_item(): void
    {
        // 'archived' rather than 'removed': the removed status only exists in
        // the MySQL enum (see 2026_02_24_000003_fix_item_status_enum)
        $item = $this->item($this->user(), 'archived');
        $buyer = $this->user();

        $this->actingAs($buyer)->get(route('items.checkout', $item))
            ->assertRedirect(route('items.show', $item));
    }

    public function test_primary_buyer_withdrawal_promotes_next_and_notifies(): void
    {
        Notification::fake();

        $seller = $this->user();
        $item = $this->item($seller, 'sale_pending');
        $primary = $this->user();
        $backup = $this->user();
        $this->joinQueue($item, $primary, 1);
        $this->joinQueue($item, $backup, 2);

        $response = $this->actingAs($primary)->delete(route('items.queue.leave', $item));

        $response->assertRedirect(route('items.show', $item));
        $this->assertDatabaseMissing('buy_queue', ['user_id' => $primary->id]);
        $this->assertDatabaseHas('buy_queue', ['user_id' => $backup->id, 'position' => 1]);
        $this->assertSame('sale_pending', $item->fresh()->status);

        Notification::assertSentTo($backup, PromotedToPrimaryBuyer::class);
        Notification::assertSentTo($seller, BuyerLeftQueue::class);
    }

    public function test_last_buyer_withdrawal_reverts_item_to_active(): void
    {
        Notification::fake();

        $item = $this->item($this->user(), 'sale_pending');
        $buyer = $this->user();
        $this->joinQueue($item, $buyer, 1);

        $this->actingAs($buyer)->delete(route('items.queue.leave', $item));

        $this->assertDatabaseCount('buy_queue', 0);
        $this->assertSame('active', $item->fresh()->status);
    }

    public function test_seller_can_remove_a_buyer_from_the_queue(): void
    {
        Notification::fake();

        $seller = $this->user();
        $item = $this->item($seller, 'sale_pending');
        $primary = $this->user();
        $backup = $this->user();
        $this->joinQueue($item, $primary, 1);
        $this->joinQueue($item, $backup, 2);

        $response = $this->actingAs($seller)
            ->from(route('items.manage-queue', $item))
            ->delete(route('items.queue.remove', [$item, $primary]));

        $response->assertRedirect(route('items.manage-queue', $item));
        $this->assertDatabaseMissing('buy_queue', ['user_id' => $primary->id]);
        $this->assertDatabaseHas('buy_queue', ['user_id' => $backup->id, 'position' => 1]);

        Notification::assertSentTo($primary, RemovedFromQueue::class);
        Notification::assertSentTo($backup, PromotedToPrimaryBuyer::class);
    }

    public function test_only_the_seller_can_remove_buyers(): void
    {
        $item = $this->item($this->user(), 'sale_pending');
        $buyer = $this->user();
        $stranger = $this->user();
        $this->joinQueue($item, $buyer, 1);

        $this->actingAs($stranger)
            ->delete(route('items.queue.remove', [$item, $buyer]))
            ->assertForbidden();

        $this->assertDatabaseHas('buy_queue', ['user_id' => $buyer->id]);
    }

    public function test_mark_sold_notifies_the_winning_buyer(): void
    {
        Notification::fake();

        $seller = $this->user();
        $item = $this->item($seller, 'sale_pending');
        $buyer = $this->user();
        $this->joinQueue($item, $buyer, 1);

        $response = $this->actingAs($seller)->post(route('items.mark-sold', $item), [
            'buyer_id' => $buyer->id,
            'final_price' => 90,
        ]);

        $response->assertRedirect();
        $this->assertSame('sold', $item->fresh()->status);
        $this->assertDatabaseHas('transactions', [
            'item_id' => $item->id,
            'buyer_id' => $buyer->id,
            'price' => 90,
        ]);

        Notification::assertSentTo($buyer, PurchaseConfirmed::class);
    }

    public function test_selling_one_unit_of_multi_stock_item_keeps_listing_live(): void
    {
        Notification::fake();

        $seller = $this->user();
        $item = $this->item($seller, 'sale_pending');
        $item->update(['quantity' => 3]);
        $winner = $this->user();
        $backup = $this->user();
        $this->joinQueue($item, $winner, 1);
        $this->joinQueue($item, $backup, 2);

        $this->actingAs($seller)->post(route('items.mark-sold', $item), [
            'buyer_id' => $winner->id,
            'final_price' => 100,
        ]);

        $item->refresh();
        $this->assertSame(2, (int) $item->quantity);
        $this->assertSame('sale_pending', $item->status); // backup still queued
        $this->assertDatabaseMissing('buy_queue', ['user_id' => $winner->id]);
        $this->assertDatabaseHas('buy_queue', ['user_id' => $backup->id, 'position' => 1]);

        Notification::assertSentTo($winner, PurchaseConfirmed::class);
        Notification::assertSentTo($backup, PromotedToPrimaryBuyer::class);
    }

    public function test_selling_last_unit_closes_listing(): void
    {
        Notification::fake();

        $seller = $this->user();
        $item = $this->item($seller, 'sale_pending');
        $buyer = $this->user();
        $this->joinQueue($item, $buyer, 1);

        $this->actingAs($seller)->post(route('items.mark-sold', $item), [
            'buyer_id' => $buyer->id,
            'final_price' => 100,
        ]);

        $this->assertSame('sold', $item->fresh()->status);
        $this->assertDatabaseCount('buy_queue', 0);
    }

    public function test_withdrawing_when_not_in_queue_shows_error(): void
    {
        $item = $this->item($this->user());
        $buyer = $this->user();

        $response = $this->actingAs($buyer)->delete(route('items.queue.leave', $item));

        $response->assertRedirect(route('items.show', $item));
        $response->assertSessionHas('error');
    }
}
