<?php

namespace Database\Seeders\Demo;

use App\Models\Catalog\BundleItem;
use App\Models\Catalog\MainCategory;
use App\Models\Catalog\Product;
use App\Models\Catalog\ProductBundle;
use App\Models\Catalog\ProductUnit;
use App\Models\Catalog\SubCategory;
use App\Models\Lookups\UnitOfMeasure;
use App\Services\CategorySkuService;
use Database\Seeders\SystemCategorySeeder;
use Illuminate\Database\Seeder;

/**
 * Generic multi-category retail catalog. Categories are deliberately chosen
 * so every catalog feature the app supports has a natural home: Electronics
 * (serials on file at receipt), Groceries (perishable lots + kg/ltr units),
 * Hardware (multi-unit box/set/mtr), Fashion (pair units), and Cafe & Bakery
 * (bundles, till channel).
 *
 * Packaging & Add-ons is seeded too, but as ordinary products under the
 * system category {@see SystemCategorySeeder} owns — the POS strip they
 * appear in is presentation, not a separate kind of thing.
 */
class CatalogSeeder extends Seeder
{
    /**
     * Product-level prices live nowhere on the product row anymore — the lot
     * owns them (docs/migration-plan.md lot-pricing migration). The demo
     * still needs one price source per product so the box units, bundles and
     * the PurchasingSeeder receipts stay internally consistent within a
     * single seeding run: this in-process book is filled at product creation
     * and read by the pieces of the demo that need a product's "typical"
     * price. It is static because the demo seeders run standalone in
     * sequence (CatalogSeeder → PurchasingSeeder → SalesSeeder → …), and it
     * is intentionally NOT persisted: a re-run of a later seeder on its own
     * falls back to freshly faked prices rather than stale ones.
     *
     * @var array<int,array{sale:float,purchase:float}> product_id => prices
     */
    public static array $priceBook = [];

    /**
     * The products whose serials are on file (they get serials at receipt
     * time in PurchasingSeeder) and the perishable ones (lots get expiry
     * dates). In-process, like $priceBook — the downstream demo seeders read
     * these sets instead of a product attribute.
     *
     * @var array<int,int>
     */
    public static array $serializedIds = [];

    /** @var array<int,int> */
    public static array $perishableIds = [];

    /**
     * @var array<string,array{code:string,vat:float,subs:array<string,array{code:string,serials?:bool,perishable?:bool,items:array<int,string>,price:array{0:float,1:float}}>}>
     */
    private const CATALOG = [
        'Electronics' => [
            'code' => 'ELEC',
            'vat' => 18,
            'subs' => [
                'Mobile Phones' => [
                    'code' => 'PHN', 'serials' => true, 'price' => [29999, 249999],
                    'items' => ['Novaris X1 Smartphone', 'Novaris X2 Pro Smartphone', 'Zentek Spark Smartphone', 'Zentek Spark Lite Smartphone', 'Orbiq Vision Smartphone', 'Halcyon Nova Smartphone', 'Vantix Edge Smartphone', 'Vantix Edge Mini Smartphone'],
                ],
                'Laptops & Computers' => [
                    'code' => 'LAP', 'serials' => true, 'price' => [69999, 349999],
                    'items' => ['Compuform Air 14 Laptop', 'Compuform Ultra 15 Laptop', 'Lexbook Pro 13 Laptop', 'Ridgeline Workstation 16 Laptop', 'Vertexa Slim 14 Laptop', 'Compuform Desktop Tower PC'],
                ],
                'Audio & Accessories' => [
                    'code' => 'AUD', 'price' => [490, 14990],
                    'items' => ['Wireless Earbuds', 'Bluetooth Speaker Portable', 'USB-C Cable 1m', 'Power Bank 10000mAh', 'Phone Case Clear', 'Screen Protector Tempered Glass', 'Car Charger Dual Port', 'HDMI Cable 2m', 'Wireless Mouse', 'Mechanical Keyboard', 'HD Webcam', 'Laptop Sleeve 14in'],
                ],
                'Home Appliances' => [
                    'code' => 'APL', 'price' => [2990, 119990],
                    'items' => ['Electric Kettle 1.7L', 'Rice Cooker 1.8L', 'Blender 600W', 'Microwave Oven 20L', 'Air Fryer 4L', 'Steam Iron', 'Vacuum Cleaner Upright', 'Toaster 2-Slice', 'Electric Fan 16in', 'Hair Dryer 1800W'],
                ],
            ],
        ],
        'Groceries & FMCG' => [
            'code' => 'GRO',
            'vat' => 0,
            'subs' => [
                'Rice & Grains' => [
                    'code' => 'RICE', 'price' => [350, 2200],
                    'items' => ['Basmati Rice 5kg', 'Red Raw Rice 5kg', 'Samba Rice 5kg', 'Wheat Flour 1kg', 'Red Lentils (Dhal) 1kg', 'Chickpeas 1kg', 'Green Gram 1kg', 'Kurakkan Flour 1kg'],
                ],
                'Cooking Oil & Ghee' => [
                    'code' => 'OIL', 'perishable' => true, 'price' => [590, 2450],
                    'items' => ['Sunflower Oil 1L', 'Refined Coconut Oil 1L', 'Vegetable Ghee 500g', 'Extra Virgin Olive Oil 500ml', 'Palm Oil 1L'],
                ],
                'Beverages' => [
                    'code' => 'BEV', 'perishable' => true, 'price' => [90, 450],
                    'items' => ['Mineral Water 1.5L', 'Orange Juice 1L', 'Cola 1.5L', 'Ginger Beer 1L', 'Iced Tea 500ml', 'Energy Drink 250ml'],
                ],
                'Canned & Packaged Foods' => [
                    'code' => 'CAN', 'perishable' => true, 'price' => [150, 950],
                    'items' => ['Canned Tuna 185g', 'Canned Baked Beans 400g', 'Instant Noodles 85g', 'Tomato Sauce 400g', 'Canned Sardines 155g', 'Cream Crackers 200g', 'Potato Chips 150g'],
                ],
                'Dairy' => [
                    'code' => 'DAIRY', 'perishable' => true, 'price' => [180, 890],
                    'items' => ['Fresh Milk 1L', 'UHT Milk 1L', 'Set Yogurt Cup 150g', 'Butter 200g', 'Cheese Slices 200g', 'Milk Powder 400g'],
                ],
            ],
        ],
        'Hardware & Tools' => [
            'code' => 'HW',
            'vat' => 18,
            'subs' => [
                'Hand Tools' => [
                    'code' => 'HAND', 'price' => [450, 6500],
                    'items' => ['Claw Hammer', 'Screwdriver Set 6pc', 'Adjustable Wrench 10in', 'Pliers Set 3pc', 'Tape Measure 5m', 'Utility Knife', 'Hand Saw', 'Spirit Level 60cm'],
                ],
                'Power Tools' => [
                    'code' => 'POWER', 'price' => [5990, 45990],
                    'items' => ['Cordless Drill 18V', 'Angle Grinder 4in', 'Jigsaw 500W', 'Circular Saw 1200W', 'Bench Grinder 6in', 'Rotary Hammer Drill'],
                ],
                'Plumbing' => [
                    'code' => 'PLUMB', 'price' => [120, 4500],
                    'items' => ['PVC Pipe 1in (per m)', 'PVC Elbow Joint', 'Ball Valve 1/2in', 'Teflon Tape', 'Pipe Wrench 14in', 'Bathroom Faucet'],
                ],
                'Electrical Fittings' => [
                    'code' => 'ELEC-F', 'price' => [95, 3200],
                    'items' => ['Circuit Breaker 20A', 'LED Bulb 9W', 'Extension Cord 5m', 'Switch Socket Combo', 'Electrical Wire 2.5mm (per roll)', 'Ceiling Fan Regulator'],
                ],
                'Paints' => [
                    'code' => 'PAINT', 'price' => [650, 8900],
                    'items' => ['Emulsion Paint 4L', 'Enamel Paint 1L', 'Paint Brush Set 3pc', 'Wood Varnish 1L', 'Primer Sealer 4L'],
                ],
            ],
        ],
        'Fashion & Footwear' => [
            'code' => 'FASH',
            'vat' => 18,
            'subs' => [
                "Men's Wear" => [
                    'code' => 'MEN', 'price' => [990, 5990],
                    'items' => ["Men's Cotton T-Shirt", "Men's Formal Shirt", "Men's Denim Jeans", "Men's Polo Shirt", "Men's Cargo Shorts"],
                ],
                "Women's Wear" => [
                    'code' => 'WOMEN', 'price' => [990, 6990],
                    'items' => ["Women's Blouse", "Women's Maxi Dress", "Women's Leggings", "Women's Cardigan", "Women's A-Line Skirt"],
                ],
                'Kids Wear' => [
                    'code' => 'KIDS', 'price' => [590, 2990],
                    'items' => ['Kids T-Shirt', 'Kids Shorts Set', 'Kids School Uniform', 'Kids Pajama Set'],
                ],
                'Footwear' => [
                    'code' => 'SHOE', 'price' => [890, 8990],
                    'items' => ["Men's Sneakers", "Women's Sandals", 'Kids School Shoes', "Men's Formal Shoes", 'Slippers Pair'],
                ],
            ],
        ],
        'Cafe & Bakery' => [
            'code' => 'CAFE',
            'vat' => 0,
            'subs' => [
                'Hot Beverages' => [
                    'code' => 'HOT', 'price' => [250, 650],
                    'items' => ['Espresso', 'Cappuccino', 'Cafe Latte', 'Black Tea', 'Milk Tea', 'Hot Chocolate'],
                ],
                'Cold Beverages' => [
                    'code' => 'COLD', 'price' => [350, 750],
                    'items' => ['Iced Coffee', 'Iced Latte', 'Fresh Lime Soda', 'Chocolate Milkshake', 'Mixed Fruit Smoothie'],
                ],
                'Bakery' => [
                    'code' => 'BAKE', 'price' => [180, 450],
                    'items' => ['Butter Croissant', 'Blueberry Muffin', 'Chocolate Brownie', 'Cinnamon Roll', 'Glazed Doughnut'],
                ],
                'Snacks' => [
                    'code' => 'SNACK', 'price' => [350, 890],
                    'items' => ['Chicken Sandwich', 'Vegetable Wrap', 'Beef Patty', 'Fish Bun', 'Cheese Toastie'],
                ],
            ],
        ],
    ];

    public function __construct(
        private readonly CategorySkuService $skus = new CategorySkuService,
    ) {}

    public function run(): void
    {
        // Idempotent, and the packaging products below need the system
        // category + its subcategories to exist whether or not this demo run
        // followed a full provision.
        $this->call(SystemCategorySeeder::class);

        $eachId = UnitOfMeasure::idOf(UnitOfMeasure::EACH);
        $kgId = UnitOfMeasure::idOf(UnitOfMeasure::KG);
        $ltrId = UnitOfMeasure::idOf(UnitOfMeasure::LTR);
        $mtrId = UnitOfMeasure::idOf(UnitOfMeasure::MTR);
        $boxId = UnitOfMeasure::idOf(UnitOfMeasure::BOX);
        $pairId = UnitOfMeasure::idOf(UnitOfMeasure::PAIR);

        $products = []; // sku => Product, for cross-referencing bundles below

        foreach (self::CATALOG as $mainName => $main) {
            $mainCategory = MainCategory::firstOrCreate(
                ['sort_code' => $main['code']],
                ['name' => $mainName, 'description' => "{$mainName} product range", 'is_active' => true],
            );

            foreach ($main['subs'] as $subName => $sub) {
                $baseUnitId = match (true) {
                    str_contains($subName, 'Rice') || str_contains($subName, 'Grains') => $kgId,
                    str_contains($subName, 'Oil') => $ltrId,
                    str_contains($subName, 'Plumbing') => $mtrId,
                    str_contains($subName, 'Footwear') => $pairId,
                    default => $eachId,
                };

                $subCategory = SubCategory::firstOrCreate(
                    ['main_category_id' => $mainCategory->id, 'sort_code' => $sub['code']],
                    ['name' => $subName, 'is_active' => true, 'default_base_unit_id' => $baseUnitId],
                );

                [$min, $max] = $sub['price'];

                foreach ($sub['items'] as $itemName) {
                    $sku = $this->skus->next($subCategory->loadMissing('mainCategory'));
                    $salePrice = round(fake()->randomFloat(2, $min, $max), 2);
                    $purchasePrice = round($salePrice * fake()->randomFloat(2, 0.55, 0.75), 2);

                    $product = Product::create([
                        'name' => $itemName,
                        'sku' => $sku,
                        'vat_rate' => $main['vat'],
                        'main_category_id' => $mainCategory->id,
                        'sub_category_id' => $subCategory->id,
                        'base_unit_id' => $baseUnitId,
                        'is_active' => true,
                        // Perishables carry the expiry flag: their GRN lines
                        // strictly need an expiry date and their lots sell
                        // FEFO; everything else sells FIFO.
                        'is_expiry_tracked' => (bool) ($sub['perishable'] ?? false),
                    ]);

                    if ($sub['serials'] ?? false) {
                        self::$serializedIds[] = $product->id;
                    }
                    if ($sub['perishable'] ?? false) {
                        self::$perishableIds[] = $product->id;
                    }

                    self::$priceBook[$product->id] = ['sale' => $salePrice, 'purchase' => $purchasePrice];
                    $products[$sku] = $product;
                }
            }
        }

        $this->addPackagingProducts($eachId);
        $this->addBoxUnits($products, $boxId);
        $this->addBundles($products);
    }

    /**
     * Packaging/add-ons for the demo. Nothing here is special-cased: they are
     * created by the same Product::create() as everything above, under the
     * system category and subcategories SystemCategorySeeder already made.
     * Fixed prices (not faked) because these are real Sri Lankan counter
     * prices a demo should show verbatim.
     */
    private function addPackagingProducts(?int $eachId): void
    {
        $packaging = MainCategory::packaging();

        if (! $packaging) {
            return;
        }

        $catalog = [
            'BAGS' => [
                ['Small Shopping Bag', 2.00],
                ['Medium Shopping Bag', 5.00],
                ['Large Shopping Bag', 10.00],
                ['Premium Shopping Bag', 20.00],
            ],
            'GIFTBOX' => [
                ['Small Gift Box', 50.00],
                ['Medium Gift Box', 100.00],
                ['Large Gift Box', 150.00],
                ['Hard Gift Box', 250.00],
            ],
            'GIFTPACK' => [
                ['Gift Wrapping Paper Sheet', 30.00],
                ['Gift Wrapping Service', 100.00],
                ['Ribbon & Bow Set', 45.00],
                ['Greeting Card', 75.00],
            ],
            'OTHR' => [
                ['Bubble Wrap Roll (per m)', 60.00],
                ['Corrugated Carton — Small', 90.00],
                ['Corrugated Carton — Large', 180.00],
            ],
        ];

        foreach ($catalog as $sortCode => $items) {
            $subCategory = SubCategory::where('main_category_id', $packaging->id)
                ->where('sort_code', $sortCode)
                ->first();

            if (! $subCategory) {
                continue;
            }

            foreach ($items as [$name, $salePrice]) {
                $product = Product::create([
                    'name' => $name,
                    'sku' => $this->skus->next($subCategory->loadMissing('mainCategory')),
                    'vat_rate' => 0,
                    'main_category_id' => $packaging->id,
                    'sub_category_id' => $subCategory->id,
                    'base_unit_id' => $eachId,
                    'is_active' => true,
                ]);

                self::$priceBook[$product->id] = ['sale' => $salePrice, 'purchase' => round($salePrice * 0.6, 2)];
            }
        }
    }

    /** @param array<string,Product> $products */
    private function addBoxUnits(array $products, ?int $boxId): void
    {
        if (! $boxId) {
            return;
        }

        foreach ($products as $product) {
            if (! str_starts_with($product->sku, 'GRO-') && ! str_starts_with($product->sku, 'HW-')) {
                continue;
            }

            if (random_int(0, 2) !== 0) {
                continue;
            }

            $caseSize = fake()->randomElement([6, 12, 24]);
            ProductUnit::create([
                'product_id' => $product->id,
                'unit_id' => $boxId,
                'conversion_factor' => $caseSize,
                'sale_price' => round((float) self::$priceBook[$product->id]['sale'] * $caseSize * 0.95, 2),
                'is_default_sale_unit' => false,
            ]);
        }
    }

    /** @param array<string,Product> $products */
    private function addBundles(array $products): void
    {
        $bundles = [
            'Breakfast Combo' => ['Butter Croissant', 'Cafe Latte'],
            'Coffee & Brownie Deal' => ['Chocolate Brownie', 'Cappuccino'],
            'Lunch Combo' => ['Chicken Sandwich', 'Iced Coffee'],
            'Snack & Smoothie Pack' => ['Cheese Toastie', 'Mixed Fruit Smoothie'],
            'Home Office Starter Pack' => ['Wireless Mouse', 'Mechanical Keyboard', 'HD Webcam'],
            'Toolkit Essentials' => ['Claw Hammer', 'Screwdriver Set 6pc', 'Tape Measure 5m'],
        ];

        $byName = [];
        foreach ($products as $product) {
            $byName[$product->name] = $product;
        }

        foreach ($bundles as $bundleName => $itemNames) {
            $components = array_filter(array_map(fn ($n) => $byName[$n] ?? null, $itemNames));
            if (count($components) < 2) {
                continue;
            }

            $retailTotal = array_sum(array_map(fn ($p) => (float) self::$priceBook[$p->id]['sale'], $components));
            $bundlePrice = round($retailTotal * 0.88, 2); // ~12% combo discount

            $bundle = ProductBundle::create([
                'name' => $bundleName,
                'sku' => 'BNDL-'.strtoupper(str_replace(' ', '', substr($bundleName, 0, 6))),
                'description' => "{$bundleName} — bundled deal",
                'sale_price' => $bundlePrice,
                'is_active' => true,
            ]);

            foreach ($components as $component) {
                BundleItem::create([
                    'bundle_id' => $bundle->id,
                    'product_id' => $component->id,
                    'quantity' => 1,
                ]);
            }
        }
    }
}
