<?php

namespace App\Http\Controllers\Api\Admin;

use App\Http\Controllers\Controller;
use App\Models\ShopBranch;
use App\Models\ShopCatalogItem;
use App\Models\ShopCategory;
use App\Models\ShopDepartment;
use App\Models\ShopProduct;
use App\Services\ShopCatalogService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;

class ShopCatalogController extends Controller
{
    public function __construct(private readonly ShopCatalogService $catalog) {}

    public function bootstrap(): JsonResponse
    {
        return response()->json(['data' => $this->catalog->bootstrap(true)]);
    }

    public function departments(): JsonResponse
    {
        return response()->json(['data' => ShopDepartment::query()
            ->with(['branches.items.products:id,name'])
            ->orderBy('sort_order')
            ->get()]);
    }

    public function storeDepartment(Request $request): JsonResponse
    {
        $validated = $this->departmentData($request);
        $validated['slug'] = $validated['slug'] ?? $this->uniqueSlug('shop_departments', $validated['name']);
        $department = ShopDepartment::query()->create($validated);

        return response()->json(['message' => 'Department created.', 'data' => $department], 201);
    }

    public function updateDepartment(Request $request, ShopDepartment $department): JsonResponse
    {
        $department->update($this->departmentData($request, $department));

        return response()->json(['message' => 'Department updated.', 'data' => $department->fresh()]);
    }

    public function destroyDepartment(ShopDepartment $department): JsonResponse
    {
        $department->delete();

        return response()->json(['message' => 'Department and its catalogue branches were deleted.']);
    }

    public function branches(Request $request): JsonResponse
    {
        $request->validate(['department_id' => ['nullable', 'integer', 'exists:shop_departments,id']]);

        return response()->json(['data' => ShopBranch::query()
            ->with('department:id,name')
            ->when($request->integer('department_id'), fn (Builder $query, int $id) => $query->where('department_id', $id))
            ->orderBy('sort_order')
            ->get()]);
    }

    public function storeBranch(Request $request): JsonResponse
    {
        $validated = $this->branchData($request);
        $validated['slug'] = $validated['slug'] ?? $this->uniqueScopedSlug(
            'shop_branches',
            'department_id',
            $validated['department_id'],
            $validated['title']
        );
        $branch = ShopBranch::query()->create($validated);

        return response()->json(['message' => 'Branch created.', 'data' => $branch], 201);
    }

    public function updateBranch(Request $request, ShopBranch $branch): JsonResponse
    {
        $branch->update($this->branchData($request, $branch));

        return response()->json(['message' => 'Branch updated.', 'data' => $branch->fresh()]);
    }

    public function destroyBranch(ShopBranch $branch): JsonResponse
    {
        $branch->delete();

        return response()->json(['message' => 'Branch deleted.']);
    }

    public function catalogItems(Request $request): JsonResponse
    {
        $request->validate(['branch_id' => ['nullable', 'integer', 'exists:shop_branches,id']]);

        return response()->json(['data' => ShopCatalogItem::query()
            ->with(['branch.department:id,name', 'products:id,name'])
            ->when($request->integer('branch_id'), fn (Builder $query, int $id) => $query->where('branch_id', $id))
            ->orderBy('sort_order')
            ->get()]);
    }

    public function storeCatalogItem(Request $request): JsonResponse
    {
        $validated = $this->catalogItemData($request);
        $productIds = Arr::pull($validated, 'product_ids', []);
        $validated['slug'] = $validated['slug'] ?? $this->uniqueScopedSlug(
            'shop_catalog_items',
            'branch_id',
            $validated['branch_id'],
            $validated['title']
        );
        $item = ShopCatalogItem::query()->create($validated);
        $this->syncCatalogProducts($item, $productIds);

        return response()->json(['message' => 'Catalogue item created.', 'data' => $item->load('products')], 201);
    }

    public function updateCatalogItem(Request $request, ShopCatalogItem $catalogItem): JsonResponse
    {
        $validated = $this->catalogItemData($request, $catalogItem);
        $productIds = Arr::pull($validated, 'product_ids', null);
        $catalogItem->update($validated);
        if ($productIds !== null) {
            $this->syncCatalogProducts($catalogItem, $productIds);
        }

        return response()->json(['message' => 'Catalogue item updated.', 'data' => $catalogItem->fresh()->load('products')]);
    }

    public function destroyCatalogItem(ShopCatalogItem $catalogItem): JsonResponse
    {
        $catalogItem->delete();

        return response()->json(['message' => 'Catalogue item deleted.']);
    }

    public function categories(): JsonResponse
    {
        return response()->json(['data' => ShopCategory::query()
            ->withCount('products')
            ->orderBy('sort_order')
            ->get()]);
    }

    public function storeCategory(Request $request): JsonResponse
    {
        $validated = $this->categoryData($request);
        $validated['slug'] = $validated['slug'] ?? $this->uniqueSlug('shop_categories', $validated['name']);
        $category = ShopCategory::query()->create($validated);

        return response()->json(['message' => 'Product category created.', 'data' => $category], 201);
    }

    public function updateCategory(Request $request, ShopCategory $category): JsonResponse
    {
        $category->update($this->categoryData($request, $category));

        return response()->json(['message' => 'Product category updated.', 'data' => $category->fresh()]);
    }

    public function destroyCategory(ShopCategory $category): JsonResponse
    {
        $category->delete();

        return response()->json(['message' => 'Product category deleted. Products were kept uncategorized.']);
    }

    public function products(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'search' => ['nullable', 'string', 'max:120'],
            'category_id' => ['nullable', 'integer', 'exists:shop_categories,id'],
            'status' => ['nullable', Rule::in(['active', 'inactive', 'featured', 'low_stock'])],
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);

        $products = ShopProduct::query()
            ->with(['category', 'images', 'variants', 'catalogItems:id'])
            ->when($validated['search'] ?? null, function (Builder $query, string $search): void {
                $query->where(function (Builder $searchQuery) use ($search): void {
                    $searchQuery
                        ->where('name', 'like', "%{$search}%")
                        ->orWhere('sku', 'like', "%{$search}%")
                        ->orWhere('brand', 'like', "%{$search}%");
                });
            })
            ->when($validated['category_id'] ?? null, fn (Builder $query, int $id) => $query->where('category_id', $id))
            ->when(($validated['status'] ?? null) === 'active', fn (Builder $query) => $query->where('is_active', true))
            ->when(($validated['status'] ?? null) === 'inactive', fn (Builder $query) => $query->where('is_active', false))
            ->when(($validated['status'] ?? null) === 'featured', fn (Builder $query) => $query->where('is_featured', true))
            ->when(($validated['status'] ?? null) === 'low_stock', fn (Builder $query) => $query
                ->where('track_inventory', true)->where('stock_quantity', '<=', 5))
            ->orderBy('sort_order')
            ->orderBy('id')
            ->paginate($validated['per_page'] ?? 20);

        return response()->json([
            'data' => collect($products->items())->map(
                fn (ShopProduct $product) => $this->catalog->product($product, true)
            ),
            'meta' => [
                'current_page' => $products->currentPage(),
                'last_page' => $products->lastPage(),
                'per_page' => $products->perPage(),
                'total' => $products->total(),
            ],
        ]);
    }

    public function showProduct(ShopProduct $product): JsonResponse
    {
        return response()->json(['data' => $this->catalog->product(
            $product->load(['category', 'images', 'variants', 'catalogItems:id', 'reviews']),
            true
        )]);
    }

    public function storeProduct(Request $request): JsonResponse
    {
        $validated = $this->productData($request);
        $product = DB::transaction(function () use ($validated): ShopProduct {
            $relations = Arr::only($validated, ['images', 'variants', 'catalog_item_ids']);
            $data = Arr::except($validated, ['images', 'variants', 'catalog_item_ids']);
            $data['slug'] = $data['slug'] ?? $this->uniqueSlug('shop_products', $data['name']);
            $product = ShopProduct::query()->create($data);
            $this->syncProductRelations($product, $relations);

            return $product;
        });

        return response()->json([
            'message' => 'Product created.',
            'data' => $this->catalog->product(
                $product->fresh()->load(['category', 'images', 'variants', 'catalogItems:id', 'reviews']),
                true
            ),
        ], 201);
    }

    public function updateProduct(Request $request, ShopProduct $product): JsonResponse
    {
        $validated = $this->productData($request, $product);
        DB::transaction(function () use ($validated, $product): void {
            $relations = Arr::only($validated, ['images', 'variants', 'catalog_item_ids']);
            $product->update(Arr::except($validated, ['images', 'variants', 'catalog_item_ids']));
            $this->syncProductRelations($product, $relations);
        });

        return response()->json([
            'message' => 'Product updated.',
            'data' => $this->catalog->product(
                $product->fresh()->load(['category', 'images', 'variants', 'catalogItems:id', 'reviews']),
                true
            ),
        ]);
    }

    public function destroyProduct(ShopProduct $product): JsonResponse
    {
        $product->delete();

        return response()->json(['message' => 'Product deleted.']);
    }

    public function toggleProduct(ShopProduct $product): JsonResponse
    {
        $product->update(['is_active' => ! $product->is_active]);

        return response()->json(['message' => 'Product status updated.', 'data' => $product->fresh()]);
    }

    public function reorderProducts(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'products' => ['required', 'array', 'min:1'],
            'products.*.id' => ['required', 'integer', 'exists:shop_products,id'],
            'products.*.sort_order' => ['required', 'integer', 'min:0'],
        ]);

        DB::transaction(function () use ($validated): void {
            foreach ($validated['products'] as $item) {
                ShopProduct::query()->whereKey($item['id'])->update(['sort_order' => $item['sort_order']]);
            }
        });

        return response()->json(['message' => 'Product order updated.']);
    }

    private function departmentData(Request $request, ?ShopDepartment $department = null): array
    {
        return $request->validate([
            'name' => ['required', 'string', 'max:160'],
            'short_name' => ['nullable', 'string', 'max:100'],
            'slug' => ['nullable', 'alpha_dash', 'max:160', Rule::unique('shop_departments', 'slug')->ignore($department?->id)],
            'kicker' => ['nullable', 'string', 'max:160'],
            'description' => ['nullable', 'string', 'max:3000'],
            'hero_image' => ['nullable', 'string', 'max:2000'],
            'hero_background_image' => ['nullable', 'string', 'max:2000'],
            'hero_content' => ['nullable', 'array'],
            'hero_content.image_caption' => ['nullable', 'string', 'max:190'],
            'hero_content.primary_button_text' => ['nullable', 'string', 'max:120'],
            'hero_content.primary_button_link' => ['nullable', 'string', 'max:1000'],
            'hero_content.secondary_button_text' => ['nullable', 'string', 'max:120'],
            'hero_content.secondary_button_link' => ['nullable', 'string', 'max:1000'],
            'hero_content.subcategory_eyebrow' => ['nullable', 'string', 'max:160'],
            'hero_content.subcategory_heading' => ['nullable', 'string', 'max:255'],
            'hero_content.subcategory_description' => ['nullable', 'string', 'max:1500'],
            'hero_content.popular_eyebrow' => ['nullable', 'string', 'max:160'],
            'hero_content.popular_heading' => ['nullable', 'string', 'max:255'],
            'hero_content.popular_description' => ['nullable', 'string', 'max:1500'],
            'hero_styles' => ['nullable', 'array'],
            'hero_styles.background_mode' => ['nullable', Rule::in(['default', 'color', 'gradient', 'image'])],
            'hero_styles.background_color' => ['nullable', 'string', 'max:40'],
            'hero_styles.gradient_start' => ['nullable', 'string', 'max:40'],
            'hero_styles.gradient_end' => ['nullable', 'string', 'max:40'],
            'hero_styles.text_color' => ['nullable', 'string', 'max:40'],
            'hero_styles.overlay_opacity' => ['nullable', 'numeric', 'between:0,0.95'],
            'hero_styles.image_position' => ['nullable', Rule::in(['center', 'top', 'bottom', 'left', 'right'])],
            'hero_styles.hero_height' => ['nullable', Rule::in(['compact', 'default', 'large'])],
            'sort_order' => ['nullable', 'integer', 'min:0'],
            'is_active' => ['nullable', 'boolean'],
        ]);
    }

    private function branchData(Request $request, ?ShopBranch $branch = null): array
    {
        $departmentId = $request->integer('department_id') ?: $branch?->department_id;

        return $request->validate([
            'department_id' => ['required', 'integer', 'exists:shop_departments,id'],
            'title' => ['required', 'string', 'max:160'],
            'slug' => ['nullable', 'alpha_dash', 'max:160', Rule::unique('shop_branches', 'slug')
                ->where(fn ($query) => $query->where('department_id', $departmentId))
                ->ignore($branch?->id)],
            'label' => ['nullable', 'string', 'max:160'],
            'description' => ['nullable', 'string', 'max:3000'],
            'image' => ['nullable', 'string', 'max:2000'],
            'sort_order' => ['nullable', 'integer', 'min:0'],
            'is_active' => ['nullable', 'boolean'],
        ]);
    }

    private function catalogItemData(Request $request, ?ShopCatalogItem $item = null): array
    {
        $branchId = $request->integer('branch_id') ?: $item?->branch_id;

        return $request->validate([
            'branch_id' => ['required', 'integer', 'exists:shop_branches,id'],
            'title' => ['required', 'string', 'max:160'],
            'slug' => ['nullable', 'alpha_dash', 'max:160', Rule::unique('shop_catalog_items', 'slug')
                ->where(fn ($query) => $query->where('branch_id', $branchId))
                ->ignore($item?->id)],
            'kind' => ['required', Rule::in(['product', 'service'])],
            'description' => ['nullable', 'string', 'max:3000'],
            'image' => ['nullable', 'string', 'max:2000'],
            'features' => ['nullable', 'array'],
            'features.*' => ['nullable', 'string', 'max:500'],
            'product_ids' => ['nullable', 'array'],
            'product_ids.*' => ['integer', 'exists:shop_products,id'],
            'sort_order' => ['nullable', 'integer', 'min:0'],
            'is_active' => ['nullable', 'boolean'],
        ]);
    }

    private function categoryData(Request $request, ?ShopCategory $category = null): array
    {
        return $request->validate([
            'name' => ['required', 'string', 'max:160'],
            'slug' => ['nullable', 'alpha_dash', 'max:160', Rule::unique('shop_categories', 'slug')->ignore($category?->id)],
            'description' => ['nullable', 'string', 'max:3000'],
            'image' => ['nullable', 'string', 'max:2000'],
            'sort_order' => ['nullable', 'integer', 'min:0'],
            'is_active' => ['nullable', 'boolean'],
        ]);
    }

    private function productData(Request $request, ?ShopProduct $product = null): array
    {
        return $request->validate([
            'category_id' => ['nullable', 'integer', 'exists:shop_categories,id'],
            'name' => ['required', 'string', 'max:190'],
            'slug' => ['nullable', 'alpha_dash', 'max:190', Rule::unique('shop_products', 'slug')->ignore($product?->id)],
            'sku' => ['nullable', 'string', 'max:120', Rule::unique('shop_products', 'sku')->ignore($product?->id)],
            'category_label' => ['nullable', 'string', 'max:160'],
            'brand' => ['nullable', 'string', 'max:160'],
            'product_type' => ['required', Rule::in(['physical', 'digital', 'service'])],
            'short_description' => ['nullable', 'string', 'max:1000'],
            'description' => ['nullable', 'string', 'max:10000'],
            'price' => ['required', 'numeric', 'min:0', 'max:999999999999'],
            'compare_at_price' => ['nullable', 'numeric', 'min:0', 'max:999999999999'],
            'cost_price' => ['nullable', 'numeric', 'min:0', 'max:999999999999'],
            'image' => ['nullable', 'string', 'max:2000'],
            'stock_quantity' => ['required', 'integer', 'min:0'],
            'track_inventory' => ['nullable', 'boolean'],
            'is_featured' => ['nullable', 'boolean'],
            'is_active' => ['nullable', 'boolean'],
            'rating' => ['nullable', 'numeric', 'between:0,5'],
            'review_count' => ['nullable', 'integer', 'min:0'],
            'base_features' => ['nullable', 'array'],
            'base_features.*' => ['nullable', 'string', 'max:500'],
            'specifications' => ['nullable', 'array'],
            'upgrade_options' => ['nullable', 'array'],
            'metadata' => ['nullable', 'array'],
            'sort_order' => ['nullable', 'integer', 'min:0'],
            'catalog_item_ids' => ['nullable', 'array'],
            'catalog_item_ids.*' => ['integer', 'exists:shop_catalog_items,id'],
            'images' => ['nullable', 'array'],
            'images.*.url' => ['required', 'string', 'max:2000'],
            'images.*.alt_text' => ['nullable', 'string', 'max:255'],
            'images.*.sort_order' => ['nullable', 'integer', 'min:0'],
            'variants' => ['nullable', 'array'],
            'variants.*.variant_id' => ['nullable', 'integer', 'exists:shop_product_variants,id'],
            'variants.*.external_id' => ['nullable', 'integer'],
            'variants.*.name' => ['required', 'string', 'max:190'],
            'variants.*.label' => ['nullable', 'string', 'max:120'],
            'variants.*.sku' => ['nullable', 'string', 'max:120'],
            'variants.*.price' => ['required', 'numeric', 'min:0'],
            'variants.*.compare_at_price' => ['nullable', 'numeric', 'min:0'],
            'variants.*.stock_quantity' => ['required', 'integer', 'min:0'],
            'variants.*.image' => ['nullable', 'string', 'max:2000'],
            'variants.*.images' => ['nullable', 'array'],
            'variants.*.features' => ['nullable', 'array'],
            'variants.*.attributes' => ['nullable', 'array'],
            'variants.*.rating' => ['nullable', 'numeric', 'between:0,5'],
            'variants.*.is_active' => ['nullable', 'boolean'],
            'variants.*.sort_order' => ['nullable', 'integer', 'min:0'],
        ]);
    }

    private function syncCatalogProducts(ShopCatalogItem $item, array $productIds): void
    {
        $sync = [];
        foreach (array_values($productIds) as $index => $productId) {
            $sync[$productId] = ['sort_order' => ($index + 1) * 10];
        }
        $item->products()->sync($sync);
    }

    private function syncProductRelations(ShopProduct $product, array $relations): void
    {
        if (array_key_exists('images', $relations)) {
            $product->images()->delete();
            foreach ($relations['images'] ?? [] as $index => $image) {
                $product->images()->create([
                    'url' => $image['url'],
                    'alt_text' => $image['alt_text'] ?? $product->name,
                    'sort_order' => $image['sort_order'] ?? (($index + 1) * 10),
                ]);
            }
        }

        if (array_key_exists('variants', $relations)) {
            $keptIds = [];
            foreach ($relations['variants'] ?? [] as $index => $variantData) {
                $variantId = Arr::pull($variantData, 'variant_id');
                $variantData['sort_order'] = $variantData['sort_order'] ?? (($index + 1) * 10);
                $variant = $variantId
                    ? $product->variants()->whereKey($variantId)->firstOrFail()
                    : $product->variants()->make();
                $variant->fill($variantData)->save();
                $keptIds[] = $variant->id;
            }
            $product->variants()->when($keptIds, fn ($query) => $query->whereNotIn('id', $keptIds))->delete();
        }

        if (array_key_exists('catalog_item_ids', $relations)) {
            $product->catalogItems()->sync($relations['catalog_item_ids'] ?? []);
        }
    }

    private function uniqueSlug(string $table, string $value): string
    {
        $base = Str::slug($value) ?: 'item';
        $slug = $base;
        $suffix = 2;
        while (DB::table($table)->where('slug', $slug)->exists()) {
            $slug = "{$base}-{$suffix}";
            $suffix++;
        }

        return $slug;
    }

    private function uniqueScopedSlug(string $table, string $scopeColumn, int $scopeId, string $value): string
    {
        $base = Str::slug($value) ?: 'item';
        $slug = $base;
        $suffix = 2;
        while (DB::table($table)->where($scopeColumn, $scopeId)->where('slug', $slug)->exists()) {
            $slug = "{$base}-{$suffix}";
            $suffix++;
        }

        return $slug;
    }
}
