<?php

namespace Database\Seeders;

use App\Domains\Catalog\Models\Attribute;
use App\Domains\Catalog\Models\Brand;
use App\Domains\Catalog\Models\Category;
use App\Domains\Catalog\Models\Product;
use App\Domains\Catalog\Services\ProductService;
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}>
     */
    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_blanco' => ['price' => 55999, 'stock' => 14],
        'pantalon_negro' => ['price' => 57999, 'stock' => 14],
        'remera_blanca' => ['price' => 35999, 'stock' => 22],
        'remera_negra' => ['price' => 36999, 'stock' => 22],
        'zapatillas_lecoq' => ['price' => 109999, 'stock' => 6],
        'zapatillas_topper' => ['price' => 104999, 'stock' => 7],
    ];

    /**
     * @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 ProductService $productService)
    {
    }

    /**
     * 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 = storage_path('public/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 $pricing, string $groupKey) use ($groupedFiles, $tenant): array {
                $filesForGroup = $groupedFiles->get($groupKey);

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

                $metadata = $this->parseGroupKey($groupKey);

                return [
                    'group_key' => $groupKey,
                    'pricing' => $pricing,
                    'tenant' => $tenant,
                    'files' => $filesForGroup->all(),
                    'metadata' => $metadata,
                ];
            })
            ->values();

        $existingProducts = Product::query()
            ->where('tenant_codigo', $tenant->codigo)
            ->whereIn('slug', $productsToSeed->pluck('metadata.product_slug'))
            ->with(['attachments', 'variants.attachments'])
            ->get();

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

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

    /**
     * @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, stock: int} $pricing
     * @param array<int, \SplFileInfo> $files
     */
    private function seedProduct(Tenant $tenant, array $metadata, array $pricing, array $files): 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'])
            ->whereNull('tenant_code')
            ->first();

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

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

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

        $product = $this->productService->create($tenant, [
            'categoria_id' => $category->id,
            'brand_id' => $brand?->id,
            'slug' => $metadata['product_slug'],
            'nombre' => $metadata['product_name'],
            'descripcion' => $metadata['description'],
            'precio' => $pricing['price'],
            'attribute_ids' => array_values($attributeIds->all()),
        ]);

        $sizes = $metadata['type'] === 'zapatillas'
            ? self::SHOE_SIZES
            : self::CLOTHING_SIZES;

        $images = array_map(
            fn (\SplFileInfo $file): UploadedFile => $this->createUploadedFile($file->getPathname()),
            $files
        );

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

            if (isset($attributeIds['color']) && $metadata['color'] !== null) {
                $definitions[] = [
                    'attribute_id' => $attributeIds['color'],
                    'value' => $metadata['color'],
                ];
            }

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

            $definitions[] = [
                'attribute_id' => $attributeIds[$sizeAttributeCode],
                'value' => $size,
            ];

            $this->productService->createVariant($product, [
                'slug' => $metadata['product_slug'].'-'.Str::slug((string) $size),
                'nombre' => $metadata['product_name'].' - Talle '.$size,
                'stock' => $pricing['stock'],
                'descripcion' => $metadata['description'],
                'precio' => $pricing['price'],
                'definitions' => $definitions,
                'images' => $images,
            ]);
        }
    }

    /**
     * @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.',
            ];
        }

        $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'], 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',
            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
        );
    }
}
