<?php

namespace App\Services\Admin\Master\Product;

use App\Concerns\HasRoleChecks;
use App\Enums\ProductStatus;
use App\Enums\Role;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use App\Services\StockMutationService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ProductService
{
    use HasRoleChecks;

    public function __construct(
        private ProductVariantService $variantService,
        private S3PresignedService $s3Service,
        private StockMutationService $stockMutationService,
    ) {}

    public function getNames(): Collection
    {
        return Product::select('name')
            ->distinct()
            ->pluck('name');
    }

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
    {
        $paginator = Product::query()
            ->select(['id', 'name', 'slug', 'description', 'status', 'rejection_reason', 'is_featured'])
            ->with([
                'categories:id,name',
                'productVariants:id,product_id,name,stock,reject_stock,retail_stock',
                'productVariants.productPrices:id,variant_id,type,price',
            ])
            ->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")
                ->orWhereHas('productVariants', fn ($vq) => $vq->where('name', 'like', "%{$search}%"))
            )
            ->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
            ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
            ->when($filters['category'] ?? null, fn ($q, $categoryId) => $q->whereHas('categories', function ($cq) use ($categoryId) {
                $cq->where('categories.id', $categoryId);
            }))
            ->when($filters['featured'] ?? null, fn ($q, $featured) => $q->where('is_featured', $featured === 'true'))
            ->when(($filters['stock'] ?? null) === 'empty', function ($q) {
                $q->whereRaw('(SELECT IFNULL(SUM(stock), 0) FROM product_variants WHERE product_variants.product_id = products.id AND product_variants.deleted_at IS NULL) = 0');
            })
            ->when(($filters['stock'] ?? null) === 'low', function ($q) {
                $q->whereRaw('(SELECT IFNULL(SUM(stock), 0) FROM product_variants WHERE product_variants.product_id = products.id AND product_variants.deleted_at IS NULL) BETWEEN 1 AND 9');
            })
            ->orderBy($sort, $direction)
            ->paginate($perPage);

        $paginator->getCollection()->each(function ($product) {
            $product->productVariants->each(function ($variant) {
                $media = $variant->getMedia('images');
                $variant->photo_urls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();
                $variant->photo_conversion_urls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath('thumb')))->toArray();
            });
        });

        return $paginator;
    }

    public function store(array $data): Product
    {
        $status = self::hasAnyRole([Role::DEVELOPER, Role::OWNER])
            ? ($data['status'] ?? ProductStatus::ACTIVE)
            : ProductStatus::PENDING;

        $product = DB::transaction(function () use ($data, $status) {
            $product = Product::create([
                'name' => $data['name'],
                'description' => $data['description'] ?? null,
                'status' => $status,
            ]);

            $product->categories()->sync($data['category_ids']);

            $useSamePrice = $data['use_same_price'] ?? false;

            // Bulk insert variants
            $now = now();
            $variantRows = collect($data['variants'])->map(fn ($v) => [
                'product_id' => $product->id,
                'name' => $v['name'],
                'stock' => $v['stock'],
                'reject_stock' => $v['reject_stock'],
                'retail_stock' => $v['retail_stock'],
                'created_at' => $now,
                'updated_at' => $now,
            ])->toArray();

            DB::table('product_variants')->insert($variantRows);

            // Map variant name -> variant ID
            $insertedVariants = ProductVariant::where('product_id', $product->id)->get();
            $variantMap = $insertedVariants->mapWithKeys(fn ($v) => [$v->name => $v->id]);

            // Bulk insert prices
            $priceRows = [];
            foreach ($data['variants'] as $variantData) {
                $variantId = $variantMap[$variantData['name']];
                $prices = $useSamePrice ? $data['shared_prices'] : $variantData['prices'];

                foreach ($prices as $priceData) {
                    $priceRows[] = [
                        'variant_id' => $variantId,
                        'type' => $priceData['type'],
                        'price' => $priceData['price'],
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                }
            }

            if ($priceRows !== []) {
                DB::table('product_prices')->insert($priceRows);
            }

            // Bulk record stock mutations
            $stockDataMap = [];
            foreach ($data['variants'] as $variantData) {
                $variantId = $variantMap[$variantData['name']];
                $stockDataMap[$variantId] = $variantData;
            }

            $this->stockMutationService->recordBulkInitial($insertedVariants, $stockDataMap, 'Stok awal saat pembuatan varian');

            // Register photos (per-variant, S3 operation)
            foreach ($data['variants'] as $variantData) {
                if (! empty($variantData['photo_keys']) && is_array($variantData['photo_keys'])) {
                    $variant = $insertedVariants->firstWhere('name', $variantData['name']);
                    $this->variantService->registerPhotos($variant, $variantData['photo_keys']);
                }
            }

            return $product;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: 'Produk Baru',
            body: "Produk \"{$product->name}\" berhasil ditambahkan".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
        );

        return $product;
    }

    public function getForEdit(Product $product): array
    {
        $product->load([
            'categories:id,name',
            'productVariants.productPrices',
            'productVariants.media',
        ]);

        $variants = $product->productVariants->map(function (ProductVariant $variant) {
            $media = $variant->getMedia('images');
            $photoKeys = $media->map(fn ($m) => $m->getCustomProperty('s3_key') ?? $m->file_name)->toArray();
            $photoUrls = $media->map(fn ($m) => $this->s3Service->getTemporaryUrl($m->getPath()))->toArray();

            return [
                'id' => $variant->id,
                'name' => $variant->name,
                'stock' => $variant->stock,
                'reject_stock' => $variant->reject_stock,
                'retail_stock' => $variant->retail_stock,
                'photo_keys' => $photoKeys,
                'photo_urls' => $photoUrls,
                'prices' => $variant->productPrices->map(fn ($p) => [
                    'type' => $p->type->value,
                    'price' => $p->price,
                ]),
            ];
        });

        return [
            'id' => $product->id,
            'name' => $product->name,
            'description' => $product->description,
            'status' => $product->status->value,
            'category_ids' => $product->categories->pluck('id'),
            'product_variants' => $variants,
        ];
    }

    public function update(Product $product, array $data): Product
    {
        $this->assertNotPending($product);

        $product = DB::transaction(function () use ($product, $data) {
            // Auto-resubmit: non-verifier editing rejected product → status becomes pending
            $newStatus = $data['status'] ?? $product->status;
            if ($product->status === ProductStatus::REJECTED && ! self::hasAnyRole([Role::DEVELOPER, Role::OWNER])) {
                $newStatus = ProductStatus::PENDING;
            }

            $product->update([
                'name' => $data['name'],
                'description' => $data['description'] ?? null,
                'status' => $newStatus,
            ]);

            $product->categories()->sync($data['category_ids']);

            $useSamePrice = $data['use_same_price'] ?? false;
            $now = now();

            // Identify existing vs new variants
            $existingVariantIds = collect($data['variants'])
                ->pluck('id')
                ->filter()
                ->toArray();

            // Delete removed variants (cascade prices + media)
            $product->productVariants()
                ->whereNotIn('id', $existingVariantIds)
                ->each(function (ProductVariant $variant) {
                    $variant->productPrices()->delete();
                    $variant->clearMediaCollection('images');
                    $variant->delete();
                });

            // Bulk load all existing variants with media (1 query instead of N)
            $existingVariantsMap = ProductVariant::whereIn('id', $existingVariantIds)
                ->with('media')
                ->get()
                ->mapWithKeys(fn ($v) => [$v->id => $v]);

            // Collect old stock data + identify changed variants
            $oldStockDataMap = [];
            $changedIds = [];
            foreach ($data['variants'] as $variantData) {
                $variantId = $variantData['id'] ?? null;
                if (! $variantId || ! isset($existingVariantsMap[$variantId])) {
                    continue;
                }
                $variant = $existingVariantsMap[$variantId];
                $oldStockDataMap[$variantId] = $variant->only(['stock', 'reject_stock', 'retail_stock']);

                if (
                    $variant->name !== $variantData['name']
                    || $variant->stock != $variantData['stock']
                    || $variant->reject_stock != $variantData['reject_stock']
                    || $variant->retail_stock != $variantData['retail_stock']
                ) {
                    $changedIds[] = $variantId;
                }
            }

            // Bulk update changed variants (only changed ones, not all)
            if ($changedIds !== []) {
                $changedUpdates = collect($data['variants'])
                    ->filter(fn ($v) => isset($v['id']) && in_array($v['id'], $changedIds));

                foreach ($changedUpdates as $variantData) {
                    $existingVariantsMap[$variantData['id']]->update([
                        'name' => $variantData['name'],
                        'stock' => $variantData['stock'],
                        'reject_stock' => $variantData['reject_stock'],
                        'retail_stock' => $variantData['retail_stock'],
                    ]);
                }
            }

            // Bulk create new variants
            $newVariantsData = collect($data['variants'])->filter(fn ($v) => ! isset($v['id']));
            $newVariantIdMap = [];

            if ($newVariantsData->isNotEmpty()) {
                $newVariantRows = $newVariantsData->map(fn ($v) => [
                    'product_id' => $product->id,
                    'name' => $v['name'],
                    'stock' => $v['stock'],
                    'reject_stock' => $v['reject_stock'],
                    'retail_stock' => $v['retail_stock'],
                    'created_at' => $now,
                    'updated_at' => $now,
                ])->toArray();

                DB::table('product_variants')->insert($newVariantRows);

                // Map name -> id for new variants
                $newlyCreated = ProductVariant::where('product_id', $product->id)
                    ->whereIn('name', $newVariantsData->pluck('name')->toArray())
                    ->get();

                $newVariantIdMap = $newlyCreated->mapWithKeys(fn ($v) => [$v->name => $v->id])->toArray();
            }

            // Build variant_id lookup: existing by id, new by name
            $variantIdLookup = [];
            foreach ($data['variants'] as $variantData) {
                $variantId = $variantData['id'] ?? $newVariantIdMap[$variantData['name']] ?? null;
                if ($variantId) {
                    $variantIdLookup[$variantData['name']] = $variantId;
                }
            }

            // Upsert all prices (1 query)
            $priceRows = [];
            foreach ($data['variants'] as $variantData) {
                $variantId = $variantData['id'] ?? $newVariantIdMap[$variantData['name']] ?? null;
                if (! $variantId) {
                    continue;
                }

                $prices = $useSamePrice ? $data['shared_prices'] : $variantData['prices'];
                foreach ($prices as $priceData) {
                    $priceRows[] = [
                        'variant_id' => $variantId,
                        'type' => $priceData['type'],
                        'price' => $priceData['price'],
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                }
            }

            if ($priceRows !== []) {
                DB::table('product_prices')->upsert(
                    $priceRows,
                    ['variant_id', 'type'],
                    ['price', 'updated_at']
                );
            }

            // Bulk record stock mutations for new variants
            if ($newVariantIdMap !== []) {
                $newStockDataMap = [];
                foreach ($newVariantsData as $variantData) {
                    $variantId = $newVariantIdMap[$variantData['name']] ?? null;
                    if ($variantId) {
                        $newStockDataMap[$variantId] = $variantData;
                    }
                }

                $newVariantModels = ProductVariant::whereIn('id', array_values($newVariantIdMap))->get();
                $this->stockMutationService->recordBulkInitial($newVariantModels, $newStockDataMap, 'Stok awal saat pembuatan varian');
            }

            // Bulk record stock mutations for changed existing variants
            if ($changedIds !== []) {
                $newDataForExisting = [];
                foreach ($data['variants'] as $variantData) {
                    $variantId = $variantData['id'] ?? null;
                    if ($variantId && in_array($variantId, $changedIds)) {
                        $newDataForExisting[$variantId] = $variantData;
                    }
                }

                $changedModels = collect($changedIds)->map(fn ($id) => $existingVariantsMap[$id])->filter();
                $this->stockMutationService->recordBulkAdjustment(
                    $changedModels,
                    $oldStockDataMap,
                    $newDataForExisting,
                    'Penyesuaian stok saat edit varian'
                );
            }

            // Register photos (per-variant, S3 operation) - only if changed
            foreach ($data['variants'] as $variantData) {
                if (empty($variantData['photo_keys']) || ! is_array($variantData['photo_keys'])) {
                    continue;
                }

                $variantId = $variantData['id'] ?? $newVariantIdMap[$variantData['name']] ?? null;
                if (! $variantId) {
                    continue;
                }

                $variant = $existingVariantsMap[$variantId] ?? ProductVariant::find($variantId);
                if (! $variant) {
                    continue;
                }

                $this->variantService->syncPhotos($variant, $variantData['photo_keys']);
            }

            return $product;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: 'Produk Diperbarui',
            body: "Produk \"{$product->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
        );

        return $product;
    }

    public function destroy(Product $product): bool
    {
        $this->assertNotPending($product);

        $result = DB::transaction(function () use ($product) {
            $product->productVariants->each(function (ProductVariant $variant) {
                $variant->productPrices()->delete();
                $variant->delete();
            });

            $product->categories()->detach();

            return $product->delete();
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: 'Produk Dihapus',
            body: "Produk \"{$product->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
        );

        return $result;
    }

    public function toggleStatus(Product $product): void
    {
        $this->assertNotPending($product);

        $product->update([
            'status' => $product->status->value === 'active' ? 'inactive' : 'active',
        ]);
    }

    public function toggleFeatured(Product $product): void
    {
        $this->assertNotPending($product);

        $product->update([
            'is_featured' => ! $product->is_featured,
        ]);
    }

    public function approve(Product $product): void
    {
        $product->update([
            'status' => ProductStatus::ACTIVE,
        ]);

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER],
            title: 'Produk Disetujui',
            body: "Produk \"{$product->name}\" telah disetujui oleh ".auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
            additionalUser: $product->createdBy ?? null,
        );
    }

    public function reject(Product $product, string $reason = ''): void
    {
        $product->update([
            'status' => ProductStatus::REJECTED,
            'rejection_reason' => $reason,
        ]);

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER],
            title: 'Produk Ditolak',
            body: "Produk \"{$product->name}\" telah ditolak oleh ".auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
            additionalUser: $product->createdBy ?? null,
        );
    }

    public function resubmit(Product $product): void
    {
        $product->update([
            'status' => ProductStatus::PENDING,
            'rejection_reason' => null,
        ]);

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER],
            title: 'Produk Diajukan Ulang',
            body: "Produk \"{$product->name}\" telah diajukan ulang oleh ".auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
        );
    }

    private function assertNotPending(Product $product): void
    {
        if ($product->status === ProductStatus::PENDING && ! self::hasAnyRole([Role::DEVELOPER, Role::OWNER])) {
            throw ValidationException::withMessages([
                'status' => 'Produk sedang menunggu verifikasi dan tidak dapat diubah.',
            ]);
        }
    }
}
