<?php

namespace Daylight\Core\Http\Controllers\Store;

use Daylight\Core\Http\Controllers\Controller;
use Daylight\Core\Http\Requests\Store\Products\StoreProductRequest;
use Daylight\Core\Http\Requests\Store\Products\UpdateProductRequest;
use Daylight\Core\Models\Enums\ProductStatus;
use Daylight\Core\Models\Option;
use Daylight\Core\Models\OptionValue;
use Daylight\Core\Models\Product;
use Daylight\Core\Models\Variant;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
use Spatie\ResponseCache\Facades\ResponseCache;

class ProductController extends Controller implements HasMiddleware
{
    public static function middleware(): array
    {
        return [
            new Middleware('can:create:products', only: ['create', 'store']),
            new Middleware('can:read:products', only: ['index', 'show']),
            new Middleware('can:update:products', only: ['edit', 'update']),
            new Middleware('can:delete:products', only: ['destroy']),
        ];
    }

    public function index(): View
    {
        return view('daylight::store.products.index');
    }

    public function show(Product $product): View
    {
        $product->load([
            'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            'labels.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            'attributeValues.attribute.translations',
            'attributeValues.translations',
            'variants',
            'variants.optionValues.option.translations',
            'variants.optionValues.translations',
            'variants.taxClass',
            'variants.media',
            'variants.prices',
            'variants.shippingClasses',
        ]);

        return view('daylight::store.products.show', [
            'product' => $product,
        ]);
    }

    public function create(): View
    {
        $categories = (daylightModel('category'))::query()
            ->with([
                'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'children.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'children.children.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            ])
            ->whereNull('parent_id')
            ->get();

        $attributes = (daylightModel('attribute'))::query()
            ->with([
                'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'values.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'categories.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            ])
            ->get();

        $brands = (daylightModel('brand'))::query()->get();

        $labels = (daylightModel('label'))::query()
            ->with([
                'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            ])
            ->get();

        return view('daylight::store.products.create', [
            'categories' => $categories,
            'brands' => $brands,
            'attributes' => $attributes,
            'labels' => $labels,
            'statuses' => ProductStatus::formattedValues(),
            'options' => $this->getOptions(),
            'variants' => $this->getVariants(),
        ]);
    }

    public function store(StoreProductRequest $request)
    {
        $product = (daylightModel('product'))::create($request->safe()->only('brand_id', 'type', 'status'));

        $downloads = collect($request->get('downloads'))->mapWithKeys(function ($media, $order) {
            return [$media['id'] => [
                'order' => $order,
                'properties' => [
                    'field' => 'downloads',
                ],
            ]];
        });

        $labelIds = array_filter((array) $request->get('labels'));

        $product->media()->sync($downloads);
        $product->categories()->sync($request->get('categories'));
        $product->labels()->sync($labelIds);
        $attributeIds = collect($request->get('attributes', []))
            ->flatten()
            ->filter()
            ->unique()
            ->values()
            ->all();
        $product->attributeValues()->sync($attributeIds);

        if ($request->get('type') === 'simple') {
            (daylightModel('variant'))::whereSku($request->get('sku'))->update([
                'product_id' => $product->id,
            ]);
        }

        collect($request->get('translations'))->each(function ($translation, $locale) use ($product) {
            $product->translations()->updateOrCreate(
                ['locale' => $locale],
                [
                    ...$translation,
                    'pros' => $translation['pros'] ?? [],
                    'cons' => $translation['cons'] ?? [],
                ]
            );
        });

        if ($request->get('type') === 'variable') {
            $this->processProductData(
                $product,
                $request->get('options'),
                $request->get('variants')
            );
        }

        $product->searchable();

        return redirect()
            ->route('daylight.store.products.show', ['product' => $product, 'locale' => $request->get('locale', app()->getLocale())])
            ->with('success', __('Product has been updated.'));
    }

    public function edit(Product $product): View
    {
        $product->loadMissing([
            'translations',
            'attributeValues',
            'options.values.translations',
        ]);

        $categories = (daylightModel('category'))::query()
            ->with([
                'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'children.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'children.children.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            ])
            ->whereNull('parent_id')
            ->get();

        $attributes = (daylightModel('attribute'))::query()
            ->with([
                'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'values.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
                'categories.translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            ])
            ->get();

        $brands = (daylightModel('brand'))::query()->get();

        $labels = (daylightModel('label'))::query()
            ->with([
                'translations' => fn ($query) => $query->where('locale', request()->get('locale', app()->getLocale())),
            ])
            ->get();

        $selectedVariants = $product->variants->map(function (Variant $variant) {
            return [
                'id' => $variant->id,
                'sku' => $variant->sku,
                'name' => $variant->name,
                'image' => $variant->media->first()?->getUrl('thumbnail'),
            ];
        });

        return view('daylight::store.products.edit', [
            'product' => $product,
            'categories' => $categories,
            'brands' => $brands,
            'attributes' => $attributes,
            'labels' => $labels,
            'statuses' => ProductStatus::formattedValues(),
            'selectedVariants' => $selectedVariants,
            'options' => $this->getOptions($product),
            'variants' => $this->getVariants($product),
        ]);
    }

    public function update(UpdateProductRequest $request, Product $product)
    {
        $downloads = collect($request->get('downloads'))->mapWithKeys(function ($media, $order) {
            return [$media['id'] => [
                'order' => $order,
                'properties' => [
                    'field' => 'downloads',
                ],
            ]];
        });

        $product->media()->sync($downloads);

        $product->update($request->safe()->only('brand_id', 'type', 'status'));
        $product->categories()->sync($request->get('categories'));
        $product->labels()->sync($request->get('labels'));
        $attributeIds = collect($request->get('attributes', []))
            ->flatten()
            ->filter()
            ->unique()
            ->values()
            ->all();
        $product->attributeValues()->sync($attributeIds);

        if ($request->get('type') === 'simple') {
            (daylightModel('variant'))::whereProductId($product->id)->update([
                'product_id' => null,
            ]);

            (daylightModel('variant'))::whereSku($request->get('sku'))->update([
                'product_id' => $product->id,
            ]);
        }

        collect($request->get('translations'))->each(function ($translation, $locale) use ($product) {
            $product->translations()->updateOrCreate(
                ['locale' => $locale],
                [
                    ...$translation,
                    'pros' => $translation['pros'] ?? [],
                    'cons' => $translation['cons'] ?? [],
                ]
            );

            ResponseCache::forget(str_ireplace(config('app.url'), '', translatedRoute('products.show', $translation['slug'])));
        });

        if ($request->get('type') === 'variable') {
            $this->processProductData(
                $product,
                $request->get('options'),
                $request->get('variants')
            );
        }

        $product->searchable();

        return redirect()
            ->route('daylight.store.products.show', ['product' => $product, 'locale' => $request->get('locale')])
            ->with('success', __('Product has been updated.'));
    }

    public function destroy(Product $product)
    {
        DB::table('cart_variant')
            ->whereIn('variant_id', $product->variants->pluck('id'))
            ->delete();

        $product->translations->each(function ($translation) {
            ResponseCache::forget(str_ireplace(config('app.url'), '', translatedRoute('products.show', $translation['slug'])));
        });

        $product->delete();

        return redirect()
            ->route('daylight.store.products.index')
            ->with('success', __('Product has been deleted.'));
    }

    private function processProductData(Product $product, array $options, array $variants)
    {
        DB::transaction(function () use ($product, $options, $variants) {
            // Process Options & Option Values
            $optionMap = [];
            $optionsToPreserve = [];

            foreach ($options as $optionOrder => $optionData) {
                $option = $product->options()->find($optionData['id']);

                if ($option) {
                    $option->update([
                        'type' => $optionData['type'],
                        'order' => $optionOrder,
                    ]);
                } else {
                    $option = $product->options()->create([
                        'type' => $optionData['type'],
                        'order' => $optionOrder,
                    ]);

                    $optionMap[$optionData['id']] = $option->id;
                }

                $optionsToPreserve[] = $option->id;

                // Store translations
                foreach ($optionData['translations'] as $locale => $translation) {
                    $option->translations()->updateOrCreate(
                        ['locale' => $locale],
                        ['name' => $translation['name']]
                    );
                }

                $optionValuesToPreserve = [];

                // Process Option Values
                foreach ($optionData['optionValues'] as $optionValueOrder => $valueData) {
                    $optionValue = $option->values()->find($valueData['id']);

                    if ($optionValue) {
                        $optionValue->update([
                            'color' => $valueData['color'] ?? null,
                            'position' => $optionValueOrder,
                        ]);
                    } else {
                        $optionValue = $option->values()->create([
                            'color' => $valueData['color'] ?? null,
                            'position' => $optionValueOrder,
                        ]);

                        $optionMap[$valueData['id']] = $optionValue->id;
                    }

                    $optionValuesToPreserve[] = $optionValue->id;

                    // Store translations
                    foreach ($valueData['translations'] as $locale => $translation) {
                        $optionValue->translations()->updateOrCreate(
                            ['locale' => $locale],
                            ['name' => $translation['name']]
                        );
                    }
                }

                $option->values()->whereNotIn('id', $optionValuesToPreserve)->delete();
            }

            $product->options()->whereNotIn('id', $optionsToPreserve)->delete();

            (daylightModel('variant'))::query()
                ->where('product_id', $product->id)
                ->whereNotIn('sku', collect($variants)->pluck('sku'))
                ->update([
                    'product_id' => null,
                ]);

            foreach ($variants as $index => $variantData) {
                // Find existing variant by SKU
                $variant = (daylightModel('variant'))::where('sku', $variantData['sku'])->first();

                if ($variant) {
                    DB::table('option_value_variant')
                        ->where('variant_id', $variant->id)
                        ->delete();

                    collect($variantData['optionValues'])
                        ->each(function ($optionValueData) use ($variant, $optionMap) {
                            $optionValueId = is_numeric($optionValueData['option_value_id'])
                                ? $optionValueData['option_value_id']
                                : $optionMap[$optionValueData['option_value_id']];

                            DB::table('option_value_variant')
                                ->insert([
                                    'option_value_id' => $optionValueId,
                                    'variant_id' => $variant->id,
                                ]);
                        });

                    $variant->update([
                        'product_id' => $product->id,
                        'order' => $index,
                    ]);
                }
            }
        });
    }

    private function getOptions(?Product $product = null)
    {
        if (old('options')) {
            return collect(json_decode(old('options'), true));
        }

        return $product
            ? $product->options->sortBy('order')->map(function ($option) {
                return [
                    'id' => $option->id,
                    'type' => $option->type,
                    'order' => $option->order,
                    'translations' => $option->translations->mapWithKeys(function ($translation) {
                        return [$translation->locale => ['name' => $translation->name]];
                    }),
                    'optionValues' => $option->values->sortBy('position')->map(function ($optionValue) {
                        return [
                            'id' => $optionValue->id,
                            'color' => $optionValue->color,
                            'order' => $optionValue->position,
                            'translations' => $optionValue->translations->mapWithKeys(function ($translation) {
                                return [$translation->locale => ['name' => $translation->name]];
                            }),
                        ];
                    })->values()->toArray(),
                ];
            })->toArray()
            : [];
    }

    private function getVariants(?Product $product = null): Collection
    {
        if (old('variants')) {
            return collect(json_decode(old('variants'), true));
        }

        return $product
            ? $product->variants->map(function (Variant $variant) {
                return [
                    'sku' => $variant->sku,
                    'optionValues' => $variant->optionValues->map(function (OptionValue $optionValue) {
                        return [
                            'option_id' => $optionValue->option_id,
                            'option_value_id' => $optionValue->id,
                        ];
                    })->toArray(),
                ];
            })
            : collect();
    }
}
