<?php

namespace Tests\Feature;

use App\Models\Branch;
use App\Models\Category;
use App\Models\Ingredient;
use App\Models\Product;
use App\Models\ProductionBatch;
use App\Models\Recipe;
use App\Models\RecipeIngredient;
use App\Models\ScoopSale;
use App\Models\User;
use App\Models\BranchProduct;
use App\Models\BatchSizeConfig;
use App\Models\StockLedger;
use App\Services\OrderService;
use App\Services\ProductionBatchService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class ProductionBatchTest extends TestCase
{
    use DatabaseTransactions;

    protected User $user;
    protected Branch $branch;
    protected Product $product;
    protected Recipe $recipe;
    protected Ingredient $ingredient;
    protected BatchSizeConfig $batchSizeConfig;

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

        // Get existing user or create one
        $user = User::first();
        if (!$user) {
            $user = User::create([
                'name' => 'Test User',
                'email' => 'test-' . uniqid() . '@example.com',
                'password' => bcrypt('password'),
            ]);
        }
        $this->user = $user;

        // Get existing branch or create one
        $branch = Branch::first();
        if (!$branch) {
            $branch = Branch::create([
                'name' => 'Test Branch',
                'code' => 'TEST',
                'address' => '123 Test Street',
                'phone' => '+1234567890',
                'is_active' => true,
            ]);
        }
        $this->branch = $branch;

        // Get existing category or create one
        $category = Category::first();
        if (!$category) {
            $category = Category::create([
                'name' => 'Ice Cream',
                'slug' => 'ice-cream',
                'is_active' => true,
            ]);
        }

        // Create test product
        $this->product = Product::create([
            'name' => 'Test Flavor ' . uniqid(),
            'slug' => 'test-flavor-' . uniqid(),
            'category_id' => $category->id,
            'base_price' => 50.00,
            'is_active' => true,
        ]);

        // Create branch product availability
        BranchProduct::create([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'is_available' => true,
        ]);

        // Get existing ingredient or create one
        $ingredient = Ingredient::first();
        if (!$ingredient) {
            $ingredient = Ingredient::create([
                'name' => 'Test Ingredient ' . uniqid(),
                'sku' => 'ING-' . uniqid(),
                'unit' => 'kg',
                'cost_per_unit' => 100.00,
                'is_active' => true,
            ]);
        }
        $this->ingredient = $ingredient;

        // Get or create stock ledger for ingredient
        $stockLedger = StockLedger::where('branch_id', $this->branch->id)
            ->where('ingredient_id', $this->ingredient->id)
            ->first();

        if (!$stockLedger) {
            StockLedger::create([
                'branch_id' => $this->branch->id,
                'ingredient_id' => $this->ingredient->id,
                'quantity' => 1000,
                'reserved_quantity' => 0,
                'average_cost' => $this->ingredient->cost_per_unit,
            ]);
        } else {
            // Ensure sufficient stock
            $stockLedger->update(['quantity' => 1000]);
        }

        // Create recipe for test product
        $this->recipe = Recipe::create([
            'product_id' => $this->product->id,
            'name' => 'Test Recipe ' . uniqid(),
            'yield_quantity' => 1,
            'is_active' => true,
        ]);

        // Add ingredient to recipe
        RecipeIngredient::create([
            'recipe_id' => $this->recipe->id,
            'ingredient_id' => $this->ingredient->id,
            'quantity' => 0.5,
            'unit' => 'kg',
        ]);

        // Get existing batch size config or create one
        $batchSizeConfig = BatchSizeConfig::where('is_active', true)->first();
        if (!$batchSizeConfig) {
            $batchSizeConfig = BatchSizeConfig::create([
                'name' => 'Small Batch',
                'size_code' => 'small',
                'size_value' => 1.0,
                'size_unit' => 'liter',
                'scoops_per_batch' => 30,
                'ingredient_multiplier' => 1.0,
                'default_expiry_hours' => 24,
                'is_active' => true,
            ]);
        }
        $this->batchSizeConfig = $batchSizeConfig;
    }

    #[Test]
    public function it_creates_production_batch_and_deducts_ingredients(): void
    {
        $service = app(ProductionBatchService::class);

        // Get stock before
        $stockBefore = StockLedger::where('branch_id', $this->branch->id)
            ->where('ingredient_id', $this->ingredient->id)
            ->first();
        $quantityBefore = (float) $stockBefore->quantity;

        // Create batch
        $batch = $service->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        // Assert batch created
        $this->assertNotNull($batch);
        $this->assertEquals('active', $batch->status);
        $this->assertGreaterThan(0, $batch->total_scoops);
        $this->assertEquals($batch->total_scoops, $batch->remaining_scoops);
        $this->assertEquals(0, $batch->sold_scoops);

        // Assert ingredient deducted
        $stockAfter = StockLedger::where('branch_id', $this->branch->id)
            ->where('ingredient_id', $this->ingredient->id)
            ->first();

        $this->assertLessThan($quantityBefore, (float) $stockAfter->quantity);
    }

    #[Test]
    public function it_deducts_scoops_when_order_is_completed(): void
    {
        $batchService = app(ProductionBatchService::class);
        $orderService = app(OrderService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        $remainingBefore = $batch->remaining_scoops;
        $soldBefore = $batch->sold_scoops;

        // Create order
        $order = $orderService->create([
            'branch_id' => $this->branch->id,
            'user_id' => $this->user->id,
            'customer_name' => 'Test Customer',
            'order_type' => 'dine_in',
            'items' => [
                [
                    'product_id' => $this->product->id,
                    'quantity' => 3,
                    'unit_price' => 50.00,
                ]
            ]
        ]);

        $this->assertEquals('pending', $order->status);

        // Complete order
        $order = $orderService->updateStatus($order->id, 'completed', $this->user->id);

        // Refresh batch
        $batch->refresh();

        // Assert scoops deducted
        $this->assertEquals($remainingBefore - 3, $batch->remaining_scoops);
        $this->assertEquals($soldBefore + 3, $batch->sold_scoops);

        // Assert scoop sale recorded
        $scoopSale = ScoopSale::where('order_id', $order->id)->first();
        $this->assertNotNull($scoopSale);
        $this->assertEquals(3, $scoopSale->scoop_count);
        $this->assertEquals($batch->id, $scoopSale->production_batch_id);
    }

    #[Test]
    public function it_does_not_deduct_scoops_for_pending_orders(): void
    {
        $batchService = app(ProductionBatchService::class);
        $orderService = app(OrderService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        $remainingBefore = $batch->remaining_scoops;

        // Create order (stays pending)
        $order = $orderService->create([
            'branch_id' => $this->branch->id,
            'user_id' => $this->user->id,
            'customer_name' => 'Test Customer',
            'order_type' => 'dine_in',
            'items' => [
                [
                    'product_id' => $this->product->id,
                    'quantity' => 2,
                    'unit_price' => 50.00,
                ]
            ]
        ]);

        // Refresh batch
        $batch->refresh();

        // Assert scoops NOT deducted (order still pending)
        $this->assertEquals($remainingBefore, $batch->remaining_scoops);

        // Assert no scoop sale recorded
        $scoopSale = ScoopSale::where('order_id', $order->id)->first();
        $this->assertNull($scoopSale);
    }

    #[Test]
    public function it_does_not_deduct_scoops_for_cancelled_orders(): void
    {
        $batchService = app(ProductionBatchService::class);
        $orderService = app(OrderService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        $remainingBefore = $batch->remaining_scoops;

        // Create and cancel order
        $order = $orderService->create([
            'branch_id' => $this->branch->id,
            'user_id' => $this->user->id,
            'customer_name' => 'Test Customer',
            'order_type' => 'dine_in',
            'items' => [
                [
                    'product_id' => $this->product->id,
                    'quantity' => 2,
                    'unit_price' => 50.00,
                ]
            ]
        ]);

        $orderService->cancel($order->id, $this->user->id, 'Customer changed mind');

        // Refresh batch
        $batch->refresh();

        // Assert scoops NOT deducted
        $this->assertEquals($remainingBefore, $batch->remaining_scoops);
    }

    #[Test]
    public function it_records_waste_correctly(): void
    {
        $batchService = app(ProductionBatchService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        $remainingBefore = $batch->remaining_scoops;
        $wastedBefore = $batch->wasted_scoops;

        // Record waste
        $batch = $batchService->recordBatchWaste($batch->id, 5, 'Dropped container');

        // Assert waste recorded
        $this->assertEquals($remainingBefore - 5, $batch->remaining_scoops);
        $this->assertEquals($wastedBefore + 5, $batch->wasted_scoops);
        $this->assertStringContainsString('Dropped container', $batch->notes ?? '');
    }

    #[Test]
    public function it_marks_batch_as_finished_when_all_scoops_sold(): void
    {
        $batchService = app(ProductionBatchService::class);
        $orderService = app(OrderService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        $totalScoops = $batch->total_scoops;

        // Create and complete order for all scoops
        $order = $orderService->create([
            'branch_id' => $this->branch->id,
            'user_id' => $this->user->id,
            'customer_name' => 'Test Customer',
            'order_type' => 'dine_in',
            'items' => [
                [
                    'product_id' => $this->product->id,
                    'quantity' => $totalScoops,
                    'unit_price' => 50.00,
                ]
            ]
        ]);

        $orderService->updateStatus($order->id, 'completed', $this->user->id);

        // Refresh batch
        $batch->refresh();

        // Assert batch finished
        $this->assertEquals(0, $batch->remaining_scoops);
        $this->assertEquals($totalScoops, $batch->sold_scoops);
        $this->assertEquals('finished', $batch->status);
    }

    #[Test]
    public function it_marks_expired_batches_and_converts_remaining_to_waste(): void
    {
        $batchService = app(ProductionBatchService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        $remainingBefore = $batch->remaining_scoops;

        // Manually set expiry to past
        $batch->update(['expires_at' => now()->subHour()]);

        // Mark expired batches
        $count = $batchService->markExpiredBatches();

        // Refresh batch
        $batch->refresh();

        // Assert batch expired
        $this->assertGreaterThanOrEqual(1, $count);
        $this->assertEquals('expired', $batch->status);
        $this->assertEquals(0, $batch->remaining_scoops);
        $this->assertEquals($remainingBefore, $batch->wasted_scoops);
    }

    #[Test]
    public function it_finds_correct_batch_for_product(): void
    {
        $batchService = app(ProductionBatchService::class);

        // Create expired batch
        $expiredBatch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);
        $expiredBatch->update(['expires_at' => now()->subHour(), 'status' => 'expired']);

        // Create active batch
        $activeBatch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        // Find available batch
        $foundBatch = $batchService->findAvailableBatchForProduct(
            $this->branch->id,
            $this->product->id
        );

        // Assert correct batch found
        $this->assertNotNull($foundBatch);
        $this->assertEquals($activeBatch->id, $foundBatch->id);
        $this->assertEquals('active', $foundBatch->status);
    }

    #[Test]
    public function it_calculates_cost_per_scoop_correctly(): void
    {
        $batchService = app(ProductionBatchService::class);

        // Create batch
        $batch = $batchService->createBatch([
            'branch_id' => $this->branch->id,
            'product_id' => $this->product->id,
            'recipe_id' => $this->recipe->id,
            'produced_by' => $this->user->id,
            'batch_size' => $this->batchSizeConfig->size_code,
            'quantity' => 1,
        ]);

        // Assert cost per scoop calculated
        $this->assertGreaterThan(0, (float) $batch->cost_per_scoop);

        // Calculate expected cost
        $totalCost = (float) $batch->ingredients->sum('total_cost');
        $expectedCostPerScoop = $totalCost / $batch->total_scoops;

        $this->assertEquals(
            round($expectedCostPerScoop, 4),
            round((float) $batch->cost_per_scoop, 4)
        );
    }
}
