<?php

namespace App\Services\Admin\Master\Product;

use App\Concerns\HasRoleChecks;
use App\Enums\PriceType;
use App\Enums\ProductStatus;
use App\Enums\Role;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use App\Services\StockMutationService;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ProductVariantService
{
    use HasRoleChecks, RegistersMedia;

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

    public function getForRestock(): array
    {
        return Product::query()
            ->select(['id', 'name', 'status'])
            ->with([
                'productVariants:id,product_id,name,stock,reject_stock',
                'productVariants.productPrices:id,variant_id,type,price',
            ])
            ->active()
            ->orderBy('name')
            ->get()
            ->each(function (Product $product) {
                $product->productVariants->each(function (ProductVariant $variant) {
                    $media = $variant->getFirstMedia('images');
                    $variant->photo_url = $media
                        ? $this->s3Service->getTemporaryUrl($media->getPath())
                        : null;

                    $capitalPrice = $variant->productPrices
                        ->first(fn ($price) => $price->type === PriceType::CAPITAL);
                    $variant->capital_price = $capitalPrice?->price ?? 0;

                    $rejectPrice = $variant->productPrices
                        ->first(fn ($price) => $price->type === PriceType::REJECT);
                    $variant->reject_price = $rejectPrice?->price ?? 0;
                });
            })->toArray();
    }

    public function getForTransaction(): array
    {
        return Product::query()
            ->select(['id', 'name', 'status'])
            ->with([
                'productVariants:id,product_id,name,stock,reject_stock',
                'productVariants.productPrices:id,variant_id,type,price',
            ])
            ->active()
            ->orderBy('name')
            ->get()
            ->each(function (Product $product) {
                $product->productVariants->each(function (ProductVariant $variant) {
                    $media = $variant->getFirstMedia('images');
                    $variant->photo_url = $media
                        ? $this->s3Service->getTemporaryUrl($media->getPath())
                        : null;

                    $prices = $variant->productPrices->mapWithKeys(fn ($p) => [$p->type->value => $p->price]);
                    $variant->prices = $prices;
                });
            })->toArray();
    }

    public function getForEdit(ProductVariant $variant): array
    {
        $variant->load([
            'productPrices',
            'media',
        ]);

        $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,
            'product_id' => $variant->product_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,
            ]),
        ];
    }

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

        DB::transaction(function () use ($variant, $data) {
            $oldData = $variant->only(['stock', 'reject_stock', 'retail_stock']);

            $variant->update([
                'name' => $data['name'],
                'stock' => $data['stock'],
                'reject_stock' => $data['reject_stock'],
                'retail_stock' => $data['retail_stock'],
            ]);

            $this->stockMutationService->recordAdjustment($variant, $oldData, $data, 'Penyesuaian stok saat edit varian');

            // Upsert prices instead of delete+recreate
            $priceRows = collect($data['prices'])->map(fn ($p) => [
                'variant_id' => $variant->id,
                'type' => $p['type'],
                'price' => $p['price'],
                'created_at' => now(),
                'updated_at' => now(),
            ])->toArray();

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

            if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) {
                $this->syncPhotos($variant, $data['photo_keys']);
            }
        });

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

        return $variant->fresh();
    }

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

        $result = DB::transaction(function () use ($variant) {
            $variant->productPrices()->delete();

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

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

        return $result;
    }

    public function transferStock(ProductVariant $variant, array $data): ProductVariant
    {
        $quantity = (int) $data['quantity'];

        $this->assertNotPending($variant->product);

        if ($variant->stock < $quantity) {
            throw ValidationException::withMessages([
                'quantity' => "Stok bagus tidak mencukupi. Stok tersedia: {$variant->stock}.",
            ]);
        }

        DB::transaction(function () use ($variant, $quantity, $data) {
            $stockBefore = $variant->stock;
            $retailBefore = $variant->retail_stock;

            $variant->update([
                'stock' => $stockBefore - $quantity,
                'retail_stock' => $retailBefore + $quantity,
            ]);

            $this->stockMutationService->recordTransfer(
                model: $variant,
                quantity: $quantity,
                fromQuality: 'good',
                toQuality: 'retail',
                fromBefore: $stockBefore,
                toBefore: $retailBefore,
                description: $data['description'] ?? 'Transfer stok bagus ke stok ecer',
            );
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::DIREKTUR, Role::ADMIN_TOKO],
            title: 'Transfer Stok',
            body: "{$quantity} unit dari varian \"{$variant->name}\" berhasil ditransfer dari stok bagus ke stok ecer".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.products.index'),
        );

        return $variant->fresh();
    }

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