<?php

namespace App\Services\Admin\Master\RawMaterial;

use App\Enums\Role;
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class RawMaterialService
{
    use RegistersMedia;

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

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

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = [], ?int $highlight = null): LengthAwarePaginator
    {
        $stockSumQuery = '(SELECT IFNULL(SUM(stock), 0) FROM raw_material_prices WHERE raw_material_prices.raw_material_id = raw_materials.id AND raw_material_prices.deleted_at IS NULL)';
        $valueSumQuery = '(SELECT IFNULL(SUM(price * stock), 0) FROM raw_material_prices WHERE raw_material_prices.raw_material_id = raw_materials.id AND raw_material_prices.deleted_at IS NULL)';

        return RawMaterial::query()
            ->select(['id', 'name', 'unit', 'is_active'])
            ->withCount('rawMaterialPrices as variants_count')
            ->selectRaw("{$stockSumQuery} as total_stock")
            ->selectRaw("{$valueSumQuery} as total_value")
            ->when($highlight, fn ($q) => $q->where('id', $highlight))
            ->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")
                ->orWhereHas('rawMaterialPrices', fn ($vq) => $vq->where('variant', 'like', "%{$search}%"))
            )
            ->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
            ->when(($filters['is_active'] ?? null) !== null && ($filters['is_active'] ?? null) !== '', function ($q) use ($filters) {
                $q->where('is_active', $filters['is_active'] === 'true');
            })
            ->when(($filters['stock'] ?? null) === 'empty', function ($q) use ($stockSumQuery) {
                $q->whereRaw("{$stockSumQuery} = 0");
            })
            ->when(($filters['stock'] ?? null) === 'low', function ($q) use ($stockSumQuery) {
                $q->whereRaw("{$stockSumQuery} BETWEEN 0.0001 AND 9.9999");
            })
            ->orderBy($sort, $direction)
            ->paginate($perPage);
    }

    public function getVariants(RawMaterial $rawMaterial): Collection
    {
        return $rawMaterial->rawMaterialPrices()
            ->select(['id', 'raw_material_id', 'variant', 'price', 'stock'])
            ->get()
            ->each(function (RawMaterialPrice $price) {
                $media = $price->getFirstMedia('images');
                if ($media) {
                    $price->photo_url = $this->s3Service->getTemporaryUrl($media->getPath());
                    $price->photo_conversion_url = $media->getGeneratedConversions()->contains('thumb')
                        ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                        : $this->s3Service->getTemporaryUrl($media->getPath());
                } else {
                    $price->photo_url = null;
                    $price->photo_conversion_url = null;
                }
            });
    }

    public function store(array $data): RawMaterial
    {
        $rawMaterial = DB::transaction(function () use ($data) {
            $rawMaterial = RawMaterial::create([
                'name' => $data['name'],
                'unit' => $data['unit'],
                'is_active' => $data['is_active'] ?? true,
            ]);

            $now = now();
            $priceRows = collect($data['variants'])->map(fn ($v) => [
                'raw_material_id' => $rawMaterial->id,
                'variant' => $v['variant'],
                'price' => $v['price'],
                'stock' => $v['stock'],
                'created_at' => $now,
                'updated_at' => $now,
            ])->toArray();

            DB::table('raw_material_prices')->insert($priceRows);

            $insertedPrices = RawMaterialPrice::where('raw_material_id', $rawMaterial->id)->get();
            $variantMap = $insertedPrices->mapWithKeys(fn ($p) => [$p->variant => $p->id]);

            foreach ($data['variants'] as $variantData) {
                if (! empty($variantData['photo_key'])) {
                    $priceId = $variantMap[$variantData['variant']];
                    $priceModel = RawMaterialPrice::find($priceId);
                    $this->registerPhoto($priceModel, $variantData['photo_key']);
                }
            }

            return $rawMaterial;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Bahan Baku Baru',
            body: "Bahan baku \"{$rawMaterial->name}\" berhasil ditambahkan".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
        );

        return $rawMaterial;
    }

    public function getForEdit(RawMaterial $rawMaterial): array
    {
        $rawMaterial->load([
            'rawMaterialPrices.media',
        ]);

        $variants = $rawMaterial->rawMaterialPrices->map(function (RawMaterialPrice $price) {
            $media = $price->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' => $price->id,
                'variant' => $price->variant,
                'price' => $price->price,
                'stock' => $price->stock,
                'photo_key' => $photoKeys[0] ?? null,
                'photo_url' => $photoUrls[0] ?? null,
            ];
        });

        return [
            'id' => $rawMaterial->id,
            'name' => $rawMaterial->name,
            'unit' => $rawMaterial->unit->value,
            'is_active' => $rawMaterial->is_active,
            'raw_material_prices' => $variants,
        ];
    }

    public function update(RawMaterial $rawMaterial, array $data): RawMaterial
    {
        $rawMaterial = DB::transaction(function () use ($rawMaterial, $data) {
            $rawMaterial->update([
                'name' => $data['name'],
                'unit' => $data['unit'],
                'is_active' => $data['is_active'] ?? $rawMaterial->is_active,
            ]);

            $existingVariantIds = collect($data['variants'])
                ->pluck('id')
                ->filter()
                ->toArray();

            $variantsToDelete = $rawMaterial->rawMaterialPrices()
                ->whereNotIn('id', $existingVariantIds)
                ->get();

            foreach ($variantsToDelete as $price) {
                if ($price->stock > 0) {
                    throw ValidationException::withMessages([
                        'variants' => "Varian \"{$price->variant}\" tidak dapat dihapus karena masih memiliki stok.",
                    ]);
                }

                $activeCuttingUsage = $price->cuttingMaterials()
                    ->whereHas('cutting', fn ($q) => $q->inProgress())
                    ->exists();

                if ($activeCuttingUsage) {
                    throw ValidationException::withMessages([
                        'variants' => "Varian \"{$price->variant}\" tidak dapat dihapus karena masih digunakan di cutting yang sedang diproses.",
                    ]);
                }

                $price->clearMediaCollection('images');
                $price->delete();
            }

            $existingPricesMap = RawMaterialPrice::whereIn('id', $existingVariantIds)
                ->with('media')
                ->get()
                ->mapWithKeys(fn ($p) => [$p->id => $p]);

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

            if ($newVariantsData->isNotEmpty()) {
                $newRows = $newVariantsData->map(fn ($v) => [
                    'raw_material_id' => $rawMaterial->id,
                    'variant' => $v['variant'],
                    'price' => $v['price'],
                    'stock' => $v['stock'],
                    'created_at' => $now,
                    'updated_at' => $now,
                ])->toArray();

                DB::table('raw_material_prices')->insert($newRows);

                $newlyCreated = RawMaterialPrice::where('raw_material_id', $rawMaterial->id)
                    ->whereIn('variant', $newVariantsData->pluck('variant')->toArray())
                    ->get();

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

            foreach ($data['variants'] as $variantData) {
                $priceId = $variantData['id'] ?? $newVariantIdMap[$variantData['variant']] ?? null;
                if (! $priceId) {
                    continue;
                }

                $priceModel = $existingPricesMap[$priceId] ?? RawMaterialPrice::find($priceId);
                if (! $priceModel) {
                    continue;
                }

                $priceModel->update([
                    'variant' => $variantData['variant'],
                    'price' => $variantData['price'],
                    'stock' => $variantData['stock'],
                ]);

                if (isset($variantData['photo_key'])) {
                    $existingKey = $priceModel->getMedia('images')->first()?->getCustomProperty('s3_key');
                    if ($existingKey !== $variantData['photo_key']) {
                        $priceModel->clearMediaCollection('images');
                        if ($variantData['photo_key']) {
                            $this->registerPhoto($priceModel, $variantData['photo_key']);
                        }
                    }
                }
            }

            return $rawMaterial;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Bahan Baku Diperbarui',
            body: "Bahan baku \"{$rawMaterial->name}\" berhasil diperbarui".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
        );

        return $rawMaterial;
    }

    public function destroy(RawMaterial $rawMaterial): bool
    {
        foreach ($rawMaterial->rawMaterialPrices as $price) {
            if ($price->stock > 0) {
                throw ValidationException::withMessages([
                    'raw_material' => "Bahan baku \"{$rawMaterial->name}\" tidak dapat dihapus karena varian \"{$price->variant}\" masih memiliki stok.",
                ]);
            }

            $activeCuttingUsage = $price->cuttingMaterials()
                ->whereHas('cutting', fn ($q) => $q->inProgress())
                ->exists();

            if ($activeCuttingUsage) {
                throw ValidationException::withMessages([
                    'raw_material' => "Bahan baku \"{$rawMaterial->name}\" tidak dapat dihapus karena varian \"{$price->variant}\" masih digunakan di cutting yang sedang diproses.",
                ]);
            }
        }

        $result = DB::transaction(function () use ($rawMaterial) {
            $rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
                $price->clearMediaCollection('images');
                $price->delete();
            });

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

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Bahan Baku Dihapus',
            body: "Bahan baku \"{$rawMaterial->name}\" berhasil dihapus".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.raw-materials.index'),
        );

        return $result;
    }

    public function toggleStatus(RawMaterial $rawMaterial): void
    {
        $rawMaterial->update([
            'is_active' => ! $rawMaterial->is_active,
        ]);

        $status = $rawMaterial->is_active ? 'diaktifkan' : 'dinonaktifkan';

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Status Bahan Baku Diubah',
            body: "Bahan baku \"{$rawMaterial->name}\" berhasil {$status}".' oleh '.auth()->user()->full_name.'.',
            url: route('admin.master.raw-materials.index', ['highlight' => $rawMaterial->id]),
        );
    }
}
