<?php

namespace Database\Seeders;

use App\Domains\Catalog\Enums\FeaturedGroupSource;
use App\Domains\Catalog\Enums\GroupLayout;
use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Catalog\Enums\ProductLayout;
use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\Brand;
use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Catalog\Models\Category;
use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Seeder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use RuntimeException;

class ProductCatalogFromImagesSeeder extends Seeder
{
    /**
     * @var array<string, array{price: int, stock?: int, variant_groups?: array<string, array{stock: int}>}>
     */
    private const PRODUCT_CATALOG = [
        'buzo_blanco_adidas' => ['price' => 89999, 'stock' => 12],
        'buzo_gris_nike' => ['price' => 94999, 'stock' => 10],
        'buzo_gris_under_armour' => ['price' => 96999, 'stock' => 8],
        'buzo_negro_adidas' => ['price' => 91999, 'stock' => 9],
        'camiseta_rosario_central' => ['price' => 69999, 'stock' => 20],
        'pantalon' => [
            'price' => 55999,
            'variant_groups' => [
                'pantalon_blanco' => ['stock' => 14],
                'pantalon_negro' => ['stock' => 14],
                'pantalon_azul' => ['stock' => 0],
            ],
        ],
        'remera' => [
            'price' => 35999,
            'variant_groups' => [
                'remera_blanca' => ['stock' => 22],
                'remera_negra' => ['stock' => 22],
                'remera_azul' => ['stock' => 0],
            ],
        ],
        'zapatillas_lecoq' => ['price' => 109999, 'stock' => 6],
        'zapatillas_topper' => ['price' => 104999, 'stock' => 7],
        'pelota_mundial' => ['price' => 45000, 'stock' => 15],
    ];

    /**
     * @var array<int, string>
     */
    private const CLOTHING_SIZES = ['S', 'M', 'L', 'XL'];

    /**
     * @var array<int, string>
     */
    private const SHOE_SIZES = ['38', '40', '42', '44'];

    /**
     * @var array<int, string>
     */
    private const IGNORED_FILES = [
        'istockphoto-1675347112-2048x2048.jpg',
    ];

    public function __construct(private readonly CatalogService $catalogService) {}

    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $tenant = Tenant::query()->where('codigo', 'sonder')->first();

        if (! $tenant instanceof Tenant) {
            throw new RuntimeException("Tenant 'sonder' not found.");
        }

        $catalogPath = public_path('images/tennants/fiesta_futbol_infantil/catalog');

        if (! is_dir($catalogPath)) {
            throw new RuntimeException("Catalog directory not found at path: {$catalogPath}");
        }

        $files = collect(File::files($catalogPath))
            ->filter(fn (\SplFileInfo $file) => ! in_array($file->getFilename(), self::IGNORED_FILES, true))
            ->values();

        if ($files->isEmpty()) {
            throw new RuntimeException("Catalog directory is empty at path: {$catalogPath}");
        }

        $groupedFiles = $files->groupBy(function (\SplFileInfo $file): string {
            return $this->normalizeGroupKey($file->getBasename('.'.$file->getExtension()));
        });

        $productsToSeed = collect(self::PRODUCT_CATALOG)
            ->map(function (array $catalogConfig, string $productKey) use ($groupedFiles): array {
                if (! isset($catalogConfig['variant_groups']) && ! array_key_exists('stock', $catalogConfig)) {
                    throw new RuntimeException("Missing stock configuration for catalog product '{$productKey}'.");
                }

                $variantGroups = collect($catalogConfig['variant_groups'] ?? [
                    $productKey => ['stock' => $catalogConfig['stock']],
                ])
                    ->map(function (array $variantConfig, string $groupKey) use ($groupedFiles): array {
                        $filesForGroup = $groupedFiles->get($groupKey);

                        if ($filesForGroup === null || $filesForGroup->isEmpty()) {
                            throw new RuntimeException("No images found for catalog group '{$groupKey}'.");
                        }

                        return [
                            'group_key' => $groupKey,
                            'stock' => $variantConfig['stock'],
                            'files' => $filesForGroup->all(),
                            'metadata' => $this->parseGroupKey($groupKey),
                        ];
                    })
                    ->values()
                    ->all();

                return [
                    'pricing' => ['price' => $catalogConfig['price']],
                    'metadata' => $this->buildProductMetadata($productKey, $variantGroups),
                    'variant_groups' => $variantGroups,
                ];
            })
            ->values();

        $productSlugsToDelete = $productsToSeed
            ->flatMap(fn (array $catalogProduct): array => [
                $catalogProduct['metadata']['product_slug'],
                ...collect($catalogProduct['variant_groups'])
                    ->pluck('group_key')
                    ->all(),
            ])
            ->unique()
            ->values();

        $existingProducts = CatalogItem::query()
            ->where('tenant_code', $tenant->codigo)
            ->whereIn('slug', $productSlugsToDelete)
            ->with('variants:id,catalog_item_id,inventory_id')
            ->get();

        foreach ($existingProducts as $existingProduct) {
            $this->catalogService->delete($existingProduct);
        }

        foreach ($productsToSeed as $catalogProduct) {
            $this->seedProduct(
                $tenant,
                $catalogProduct['metadata'],
                $catalogProduct['pricing'],
                $catalogProduct['variant_groups'],
            );
        }

        $this->seedFeaturedProducts($tenant);
    }

    private function seedFeaturedProducts(Tenant $tenant): void
    {
        FeaturedGroup::query()->where('tenant_code', $tenant->codigo)->delete();

        FeaturedGroup::query()->create([
            'tenant_code' => $tenant->codigo,
            'code' => 'productos',
            'source_type' => FeaturedGroupSource::All,
            'product_layout' => ProductLayout::ColumnWithImage,
            'group_layout' => GroupLayout::Paginated,
            'group_name' => 'Productos',
            'group_order' => 2,
        ]);

        $carouselGroup = FeaturedGroup::query()->create([
            'tenant_code' => $tenant->codigo,
            'code' => 'productos-destacados',
            'source_type' => FeaturedGroupSource::Manual,
            'product_layout' => ProductLayout::ColumnWithImage,
            'group_layout' => GroupLayout::Carousel,
            'group_name' => 'Productos destacados',
            'group_order' => 1,
        ]);

        $randomItems = CatalogItem::query()
            ->where('tenant_code', $tenant->codigo)
            ->inRandomOrder()
            ->limit(5)
            ->get('id');

        $carouselGroup->featuredItems()->createMany(
            $randomItems->values()->map(
                fn (CatalogItem $item, int $order): array => [
                    'catalog_item_id' => $item->id,
                    'order' => $order,
                ]
            )->all()
        );
    }

    /**
     * @param  array{attribute_codes: array<int, string>, brand_name: string|null, category_name: string, color: string|null, description: string, product_name: string, product_slug: string, type: string}  $metadata
     * @param  array{price: int}  $pricing
     * @param  array<int, array{files: array<int, \SplFileInfo>, group_key: string, metadata: array{attribute_codes: array<int, string>, brand_name: string|null, category_name: string, color: string|null, description: string, product_name: string, product_slug: string, type: string}, stock: int}>  $variantGroups
     */
    private function seedProduct(Tenant $tenant, array $metadata, array $pricing, array $variantGroups): void
    {
        $brand = null;

        if ($metadata['brand_name'] !== null) {
            $brand = Brand::query()
                ->where('tenant_codigo', $tenant->codigo)
                ->where('nombre', $metadata['brand_name'])
                ->first();

            if (! $brand instanceof Brand) {
                throw new RuntimeException("Brand '{$metadata['brand_name']}' not found for tenant '{$tenant->codigo}'.");
            }
        }

        $category = Category::query()
            ->where('nombre', $metadata['category_name'])
            ->where('tenant_code', $tenant->codigo)
            ->first();

        if (! $category instanceof Category) {
            throw new RuntimeException("Category '{$metadata['category_name']}' not found for tenant '{$tenant->codigo}'.");
        }

        $attributeCodes = Attribute::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->whereIn('codigo', $metadata['attribute_codes'])
            ->pluck('codigo');

        if ($attributeCodes->count() !== count($metadata['attribute_codes'])) {
            throw new RuntimeException("Missing attributes for product '{$metadata['product_slug']}'.");
        }

        $sizes = $metadata['type'] === 'zapatillas'
            ? self::SHOE_SIZES
            : self::CLOTHING_SIZES;
        $variants = [];
        $itemImages = [];
        $directStock = 0;

        foreach ($variantGroups as $variantGroup) {
            $images = array_map(
                fn (\SplFileInfo $file): UploadedFile => $this->createUploadedFile($file->getPathname()),
                $variantGroup['files']
            );

            if ($attributeCodes->isEmpty()) {
                $directStock = $variantGroup['stock'];
                $itemImages = [...$itemImages, ...$images];

                continue;
            }

            foreach ($sizes as $size) {
                $values = [];

                if ($attributeCodes->contains('color') && $variantGroup['metadata']['color'] !== null) {
                    $values['color'] = $variantGroup['metadata']['color'];
                }

                $sizeAttributeCode = $metadata['type'] === 'zapatillas'
                    ? 'talle_numerico'
                    : 'talle';

                if (! $attributeCodes->contains($sizeAttributeCode)) {
                    throw new RuntimeException("Missing product attribute for size on product '{$metadata['product_slug']}'.");
                }

                $values[$sizeAttributeCode] = $size;

                // Override stock for specific variants as requested by the user
                $stock = $variantGroup['stock'];
                $productType = $metadata['type'];
                $color = $variantGroup['metadata']['color'];

                if ($productType === 'remera' && $color === 'Blanco' && $size === 'L') {
                    $stock = 0;
                } elseif ($productType === 'remera' && $color === 'Negro' && $size === 'S') {
                    $stock = 0;
                } elseif ($productType === 'pantalon' && $color === 'Negro' && $size === 'XL') {
                    $stock = 0;
                } elseif ($productType === 'pantalon' && $color === 'Blanco' && $size === 'M') {
                    $stock = 0;
                }

                $variants[] = [
                    'real_stock' => $stock,
                    'values' => $values,
                    'images' => $images,
                ];
            }
        }

        $this->catalogService->create([
            'tenant_code' => $tenant->codigo,
            'category_id' => $category->id,
            'brand_id' => $brand?->id,
            'slug' => $metadata['product_slug'],
            'nombre' => $metadata['product_name'],
            'descripcion' => $metadata['description'],
            'precio' => $pricing['price'],
            'inventory_policy' => InventoryPolicy::Tracked->value,
            ...($attributeCodes->isEmpty()
                ? ['real_stock' => $directStock, 'images' => $itemImages]
                : [
                    'attribute_codes' => array_values($metadata['attribute_codes']),
                    'variants' => $variants,
                ]),
        ]);
    }

    /**
     * @param  array<int, array{files: array<int, \SplFileInfo>, group_key: string, metadata: array{attribute_codes: array<int, string>, brand_name: string|null, category_name: string, color: string|null, description: string, product_name: string, product_slug: string, type: string}, stock: int}>  $variantGroups
     * @return array{attribute_codes: array<int, string>, brand_name: string|null, category_name: string, color: string|null, description: string, product_name: string, product_slug: string, type: string}
     */
    private function buildProductMetadata(string $productKey, array $variantGroups): array
    {
        $metadata = $this->parseGroupKey($productKey);
        $hasColorVariants = collect($variantGroups)
            ->contains(fn (array $variantGroup): bool => $variantGroup['metadata']['color'] !== null);

        if ($hasColorVariants && ! in_array('color', $metadata['attribute_codes'], true)) {
            array_unshift($metadata['attribute_codes'], 'color');
        }

        if ($hasColorVariants && $metadata['color'] === null) {
            $category = Str::lower($metadata['category_name']);
            $brandText = $metadata['brand_name'] !== null ? " {$metadata['brand_name']}" : '';
            $metadata['description'] = "Producto seed de {$category}{$brandText} con variantes por color y talle.";
        }

        return $metadata;
    }

    /**
     * @return array{attribute_codes: array<int, string>, brand_name: string|null, category_name: string, color: string|null, description: string, product_name: string, product_slug: string, type: string}
     */
    private function parseGroupKey(string $groupKey): array
    {
        if ($groupKey === 'camiseta_rosario_central') {
            return [
                'type' => 'camiseta',
                'category_name' => 'Remeras',
                'brand_name' => 'Rosario Central',
                'color' => null,
                'attribute_codes' => ['talle'],
                'product_slug' => $groupKey,
                'product_name' => 'Camiseta Rosario Central',
                'description' => 'Camiseta Rosario Central con variantes por talle.',
            ];
        }

        if ($groupKey === 'pelota_mundial') {
            return [
                'type' => 'pelota',
                'category_name' => 'Accesorios',
                'brand_name' => 'Adidas',
                'color' => null,
                'attribute_codes' => [],
                'product_slug' => $groupKey,
                'product_name' => 'Pelota Mundial 2026',
                'description' => 'Pelota oficial del mundial 2026 sin variantes.',
            ];
        }

        $segments = explode('_', $groupKey);
        $type = array_shift($segments);

        if (! is_string($type) || $type === '') {
            throw new RuntimeException("Unable to infer product type from '{$groupKey}'.");
        }

        $color = null;
        $brandSegments = $segments;

        if (isset($segments[0]) && in_array($segments[0], ['blanco', 'blanca', 'negro', 'negra', 'gris', 'azul'], true)) {
            $color = $this->normalizeColor($segments[0]);
            $brandSegments = array_slice($segments, 1);
        }

        $brandKey = implode('_', $brandSegments);
        $brandName = $brandKey !== ''
            ? $this->normalizeBrandName($brandKey)
            : null;
        $categoryName = $this->resolveCategoryName($type);
        $productName = $this->buildProductName($type, $color, $brandName);
        $description = $this->buildDescription($type, $color, $brandName);
        $attributeCodes = $type === 'zapatillas'
            ? ['talle_numerico']
            : ['talle'];

        if ($color !== null) {
            array_unshift($attributeCodes, 'color');
        }

        return [
            'type' => $type,
            'category_name' => $categoryName,
            'brand_name' => $brandName,
            'color' => $color,
            'attribute_codes' => $attributeCodes,
            'product_slug' => $groupKey,
            'product_name' => $productName,
            'description' => $description,
        ];
    }

    private function normalizeGroupKey(string $basename): string
    {
        $normalized = Str::of($basename)
            ->lower()
            ->replace('-', '_')
            ->replace(' ', '_')
            ->replaceMatches('/_\d+$/', '')
            ->value();

        return str_replace('nije', 'nike', $normalized);
    }

    private function normalizeColor(string $color): string
    {
        return match ($color) {
            'blanco', 'blanca' => 'Blanco',
            'negro', 'negra' => 'Negro',
            'gris' => 'Gris',
            'azul' => 'Azul',
            default => throw new RuntimeException("Unsupported color '{$color}'."),
        };
    }

    private function normalizeBrandName(string $brandKey): string
    {
        return match ($brandKey) {
            'adidas' => 'Adidas',
            'nike' => 'Nike',
            'under_armour' => 'Under Armour',
            'topper' => 'Topper',
            'lecoq' => 'Le Coq',
            default => throw new RuntimeException("Unsupported brand '{$brandKey}'."),
        };
    }

    private function resolveCategoryName(string $type): string
    {
        return match ($type) {
            'buzo' => 'Buzos',
            'remera', 'camiseta' => 'Remeras',
            'pantalon' => 'Pantalones',
            'zapatillas' => 'Zapatillas',
            default => throw new RuntimeException("Unsupported product type '{$type}'."),
        };
    }

    private function buildProductName(string $type, ?string $color, ?string $brandName): string
    {
        $typeLabel = match ($type) {
            'buzo' => 'Buzo',
            'remera' => 'Remera',
            'camiseta' => 'Camiseta',
            'pantalon' => 'Pantalon',
            'zapatillas' => 'Zapatillas',
            default => Str::headline($type),
        };

        return trim(collect([$typeLabel, $color, $brandName])->filter()->implode(' '));
    }

    private function buildDescription(string $type, ?string $color, ?string $brandName): string
    {
        $category = Str::lower($this->resolveCategoryName($type));
        $colorText = $color !== null ? " color {$color}" : '';
        $brandText = $brandName !== null ? " {$brandName}" : '';

        return "Producto seed de {$category}{$brandText}{$colorText} con variantes por talle.";
    }

    private function createUploadedFile(string $path): UploadedFile
    {
        $mimeType = mime_content_type($path);

        return new UploadedFile(
            $path,
            basename($path),
            is_string($mimeType) ? $mimeType : null,
            null,
            true
        );
    }
}
