<?php

namespace App\Services\Admin\Manage;

use App\Enums\CuttingStatus;
use App\Enums\Role;
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingMaterialCombination;
use App\Models\CuttingResult;
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 CuttingService
{
    use RegistersMedia;

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

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = [], ?int $highlight = null): LengthAwarePaginator
    {
        $materialsCountQuery = '(SELECT COUNT(*) FROM cutting_materials WHERE cutting_materials.cutting_id = cuttings.id AND cutting_materials.deleted_at IS NULL)';
        $totalUsageQuery = '(SELECT IFNULL(SUM(material_usage), 0) FROM cutting_materials WHERE cutting_materials.cutting_id = cuttings.id AND cutting_materials.deleted_at IS NULL)';
        $productNameQuery = '(SELECT product_name FROM cutting_results WHERE cutting_results.cutting_id = cuttings.id AND cutting_results.deleted_at IS NULL LIMIT 1)';
        $cuttingResultQuery = '(SELECT cutting_result FROM cutting_results WHERE cutting_results.cutting_id = cuttings.id AND cutting_results.deleted_at IS NULL LIMIT 1)';

        $paginator = Cutting::query()
            ->select(['id', 'created_by_id', 'status', 'description', 'total_material_cost', 'cost_per_unit', 'created_at'])
            ->with([
                'createdBy:id',
                'createdBy.userProfile:id,user_id,full_name',
            ])
            ->selectRaw("{$materialsCountQuery} as materials_count")
            ->selectRaw("{$totalUsageQuery} as total_usage")
            ->selectRaw("{$productNameQuery} as product_name")
            ->selectRaw("{$cuttingResultQuery} as cutting_result")
            ->when($highlight, fn ($q) => $q->where('id', $highlight))
            ->when($search, function ($q) use ($search) {
                $q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', 'like', "%{$search}%"))
                    ->orWhere('description', 'like', "%{$search}%");
            })
            ->when($filters['product_name'] ?? null, fn ($q, $productName) => $q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', $productName)))
            ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
            ->orderBy($sort, $direction)
            ->paginate($perPage);

        $paginator->getCollection()->each(function (Cutting $cutting) {
            $cuttingMedia = $cutting->getMedia('images');
            $cutting->photo_urls = $cuttingMedia->map(
                fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
            )->toArray();
            $cutting->photo_conversion_urls = $cuttingMedia->map(
                fn ($media) => $media->getGeneratedConversions()->contains('thumb')
                    ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                    : $this->s3Service->getTemporaryUrl($media->getPath())
            )->toArray();
        });

        return $paginator;
    }

    public function getMaterials(Cutting $cutting): Collection
    {
        return $cutting->cuttingMaterials()
            ->select(['id', 'cutting_id', 'raw_material_price_id', 'material_usage', 'material_result', 'combination_id'])
            ->with([
                'rawMaterialPrice:id,raw_material_id,variant',
                'rawMaterialPrice.rawMaterial:id,name,unit',
            ])
            ->get()
            ->each(function (CuttingMaterial $material) {
                if (! $material->rawMaterialPrice) {
                    return;
                }

                $media = $material->rawMaterialPrice->getFirstMedia('images');
                $material->rawMaterialPrice->photo_url = $media
                    ? $this->s3Service->getTemporaryUrl($media->getPath())
                    : null;
                $material->rawMaterialPrice->photo_conversion_url = $media
                    ? ($media->getGeneratedConversions()->contains('thumb')
                        ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                        : $this->s3Service->getTemporaryUrl($media->getPath()))
                    : null;
            });
    }

    public function getCombinations(Cutting $cutting): Collection
    {
        return $cutting->cuttingMaterialCombinations()
            ->select(['id', 'cutting_id', 'material_result'])
            ->get();
    }

    public function getProductNames(): Collection
    {
        return CuttingResult::query()
            ->select('product_name')
            ->whereNotNull('product_name')
            ->where('product_name', '!=', '')
            ->distinct()
            ->orderBy('product_name')
            ->get();
    }

    public function getForShow(Cutting $cutting): array
    {
        $cutting->load([
            'createdBy:id',
            'createdBy.userProfile:id,user_id,full_name',
            'cuttingResults:id,cutting_id,product_name,cutting_result,sample,original_outside_sample',
            'cuttingMaterials:id,cutting_id,raw_material_price_id,material_usage,material_result,combination_id',
            'cuttingMaterials.rawMaterialPrice:id,raw_material_id,variant',
            'cuttingMaterials.rawMaterialPrice.rawMaterial:id,name,unit',
            'cuttingMaterialCombinations:id,cutting_id,material_result',
        ]);

        $cuttingMedia = $cutting->getMedia('images');
        $cutting->photo_urls = $cuttingMedia->map(
            fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
        )->toArray();
        $cutting->photo_conversion_urls = $cuttingMedia->map(
            fn ($media) => $media->getGeneratedConversions()->contains('thumb')
                ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                : $this->s3Service->getTemporaryUrl($media->getPath())
        )->toArray();

        $cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
            $media = $material->rawMaterialPrice?->getFirstMedia('images');
            if ($material->rawMaterialPrice) {
                $material->rawMaterialPrice->photo_url = $media
                    ? $this->s3Service->getTemporaryUrl($media->getPath())
                    : null;
                $material->rawMaterialPrice->photo_conversion_url = $media
                    ? ($media->getGeneratedConversions()->contains('thumb')
                        ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                        : $this->s3Service->getTemporaryUrl($media->getPath()))
                    : null;
            }
        });

        return [
            'id' => $cutting->id,
            'status' => $cutting->status->value,
            'status_label' => $cutting->status_label,
            'description' => $cutting->description,
            'total_material_cost' => $cutting->total_material_cost,
            'formatted_total_material_cost' => $cutting->formatted_total_material_cost,
            'cost_per_unit' => $cutting->cost_per_unit,
            'formatted_cost_per_unit' => $cutting->formatted_cost_per_unit,
            'created_at' => $cutting->created_at,
            'formatted_created_at' => $cutting->formatted_created_at,
            'photo_urls' => $cutting->photo_urls,
            'photo_conversion_urls' => $cutting->photo_conversion_urls,
            'created_by' => [
                'id' => $cutting->createdBy->id,
                'user_profile' => [
                    'full_name' => $cutting->createdBy->userProfile->full_name ?? '-',
                ],
            ],
            'cutting_results' => $cutting->cuttingResults->map(fn ($r) => [
                'id' => $r->id,
                'product_name' => $r->product_name,
                'cutting_result' => $r->cutting_result,
                'sample' => $r->sample,
                'original_outside_sample' => $r->original_outside_sample,
            ]),
            'cutting_materials' => $cutting->cuttingMaterials->map(fn ($m) => [
                'id' => $m->id,
                'material_usage' => $m->material_usage,
                'material_result' => $m->material_result,
                'combination_id' => $m->combination_id,
                'raw_material_price' => [
                    'id' => $m->rawMaterialPrice->id,
                    'variant' => $m->rawMaterialPrice->variant,
                    'price' => $m->rawMaterialPrice->price,
                    'photo_url' => $m->rawMaterialPrice->photo_url,
                    'photo_conversion_url' => $m->rawMaterialPrice->photo_conversion_url,
                    'raw_material' => [
                        'id' => $m->rawMaterialPrice->rawMaterial->id,
                        'name' => $m->rawMaterialPrice->rawMaterial->name,
                        'unit' => $m->rawMaterialPrice->rawMaterial->unit,
                    ],
                ],
            ]),
            'cutting_material_combinations' => $cutting->cuttingMaterialCombinations->map(fn ($c) => [
                'id' => $c->id,
                'material_result' => $c->material_result,
            ]),
        ];
    }

    public function getForEdit(Cutting $cutting): array
    {
        $cutting->load([
            'cuttingResults',
            'cuttingMaterials.rawMaterialPrice.rawMaterial',
            'cuttingMaterialCombinations',
        ]);

        $result = $cutting->cuttingResults->first();

        $materials = $cutting->cuttingMaterials->map(function (CuttingMaterial $material) {
            $media = $material->rawMaterialPrice?->getFirstMedia('images');

            return [
                'id' => $material->id,
                'raw_material_price_id' => $material->raw_material_price_id,
                'material_usage' => $material->material_usage,
                'material_result' => $material->material_result,
                'combination_id' => $material->combination_id,
                'variant' => $material->rawMaterialPrice?->variant,
                'photo_url' => $media
                    ? $this->s3Service->getTemporaryUrl($media->getPath())
                    : null,
            ];
        });

        $combinations = $cutting->cuttingMaterialCombinations->map(function (CuttingMaterialCombination $combination) use ($materials) {
            $materialIndices = $materials
                ->filter(fn ($m) => $m['combination_id'] === $combination->id)
                ->keys()
                ->values();

            return [
                'id' => $combination->id,
                'material_result' => $combination->material_result,
                'material_indices' => $materialIndices,
            ];
        });

        $cuttingMedia = $cutting->getMedia('images');
        $photoKeys = $cuttingMedia->map(
            fn ($media) => $media->getCustomProperty('s3_key') ?? $media->file_name
        )->toArray();
        $photoUrls = $cuttingMedia->map(
            fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
        )->toArray();

        return [
            'id' => $cutting->id,
            'status' => $cutting->status->value,
            'description' => $cutting->description,
            'product_name' => $result?->product_name ?? '',
            'sample' => $result?->sample ?? 0,
            'original_outside_sample' => $result?->original_outside_sample ?? 0,
            'cutting_result' => $result?->cutting_result ?? 0,
            'materials' => $materials,
            'combinations' => $combinations,
            'photo_keys' => $photoKeys,
            'photo_urls' => $photoUrls,
        ];
    }

    public function store(array $data): Cutting
    {
        $cutting = DB::transaction(function () use ($data) {
            $priceIds = collect($data['materials'])->pluck('raw_material_price_id')->unique()->values();
            $pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');

            foreach ($data['materials'] as $materialData) {
                $usage = (float) ($materialData['material_usage'] ?? 0);
                if ($usage <= 0) {
                    continue;
                }
                $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
                if (! $price) {
                    continue;
                }
                if ($usage > $price->stock) {
                    throw ValidationException::withMessages([
                        'materials' => "Stok {$price->variant} tidak mencukupi. Tersedia: {$price->stock}, dibutuhkan: {$usage}.",
                    ]);
                }
            }

            $cutting = Cutting::create([
                'created_by_id' => auth()->id(),
                'status' => 'in_progress',
                'description' => $data['description'] ?? null,
            ]);

            $totalMaterialCost = 0;
            $now = now();

            $combinations = $data['combinations'] ?? [];
            $combinationMap = [];

            foreach ($combinations as $index => $combo) {
                $combination = CuttingMaterialCombination::create([
                    'cutting_id' => $cutting->id,
                    'user_id' => auth()->id(),
                    'material_result' => $combo['material_result'] ?? null,
                    'created_at' => $now,
                    'updated_at' => $now,
                ]);
                $combinationMap[$index] = $combination->id;
            }

            $stockDecrementMap = [];

            foreach ($data['materials'] as $materialData) {
                $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
                $materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0;
                $totalMaterialCost += $materialCost;

                $combinationId = null;
                if (isset($materialData['combination_index']) && isset($combinationMap[$materialData['combination_index']])) {
                    $combinationId = $combinationMap[$materialData['combination_index']];
                }

                CuttingMaterial::create([
                    'cutting_id' => $cutting->id,
                    'user_id' => auth()->id(),
                    'raw_material_price_id' => $materialData['raw_material_price_id'],
                    'material_usage' => $materialData['material_usage'] ?? 0,
                    'material_result' => $materialData['material_result'] ?? null,
                    'combination_id' => $combinationId,
                    'created_at' => $now,
                    'updated_at' => $now,
                ]);

                $usage = (float) ($materialData['material_usage'] ?? 0);
                if ($price && $usage > 0) {
                    $stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
                }
            }

            foreach ($stockDecrementMap as $priceId => $totalUsage) {
                RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalUsage);
            }

            $costPerUnit = 0;
            if (($data['cutting_result'] ?? 0) > 0) {
                $costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']);
            }

            $cutting->update([
                'total_material_cost' => $totalMaterialCost,
                'cost_per_unit' => $costPerUnit,
            ]);

            CuttingResult::create([
                'cutting_id' => $cutting->id,
                'user_id' => auth()->id(),
                'product_name' => $data['product_name'] ?? null,
                'sample' => $data['sample'] ?? null,
                'original_outside_sample' => $data['original_outside_sample'] ?? null,
                'cutting_result' => $data['cutting_result'] ?? null,
                'created_at' => $now,
                'updated_at' => $now,
            ]);

            if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) {
                $this->registerPhotos(
                    model: $cutting,
                    photoKeys: $data['photo_keys'],
                    collectionName: 'images',
                );
            }

            return $cutting;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Cutting Baru',
            body: 'Cutting berhasil ditambahkan oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.cuttings.index', ['highlight' => $cutting->id]),
        );

        return $cutting;
    }

    public function update(Cutting $cutting, array $data): Cutting
    {
        $cutting = DB::transaction(function () use ($cutting, $data) {
            $oldMaterialUsages = $cutting->cuttingMaterials()
                ->select('raw_material_price_id', 'material_usage')
                ->where('material_usage', '>', 0)
                ->get()
                ->filter(fn ($m) => $m->raw_material_price_id)
                ->groupBy('raw_material_price_id')
                ->map(fn ($group) => $group->sum('material_usage'));

            if ($oldMaterialUsages->isNotEmpty()) {
                foreach ($oldMaterialUsages as $priceId => $totalUsage) {
                    RawMaterialPrice::where('id', $priceId)->increment('stock', $totalUsage);
                }
            }

            $priceIds = collect($data['materials'])->pluck('raw_material_price_id')->unique()->values();
            $pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');

            foreach ($data['materials'] as $materialData) {
                $usage = (float) ($materialData['material_usage'] ?? 0);
                if ($usage <= 0) {
                    continue;
                }
                $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
                if (! $price) {
                    continue;
                }
                if ($usage > $price->stock) {
                    throw ValidationException::withMessages([
                        'materials' => "Stok {$price->variant} tidak mencukupi. Tersedia: {$price->stock}, dibutuhkan: {$usage}.",
                    ]);
                }
            }

            $cutting->cuttingResults()->delete();
            $cutting->cuttingMaterials()->delete();
            $cutting->cuttingMaterialCombinations()->delete();

            $totalMaterialCost = 0;
            $now = now();

            $combinations = $data['combinations'] ?? [];
            $combinationMap = [];

            foreach ($combinations as $index => $combo) {
                $combination = CuttingMaterialCombination::create([
                    'cutting_id' => $cutting->id,
                    'user_id' => auth()->id(),
                    'material_result' => $combo['material_result'] ?? null,
                    'created_at' => $now,
                    'updated_at' => $now,
                ]);
                $combinationMap[$index] = $combination->id;
            }

            $stockDecrementMap = [];

            foreach ($data['materials'] as $materialData) {
                $price = $pricesMap[$materialData['raw_material_price_id']] ?? null;
                $materialCost = $price ? $price->price * ($materialData['material_usage'] ?? 0) : 0;
                $totalMaterialCost += $materialCost;

                $combinationId = null;
                if (isset($materialData['combination_index']) && isset($combinationMap[$materialData['combination_index']])) {
                    $combinationId = $combinationMap[$materialData['combination_index']];
                }

                CuttingMaterial::create([
                    'cutting_id' => $cutting->id,
                    'user_id' => auth()->id(),
                    'raw_material_price_id' => $materialData['raw_material_price_id'],
                    'material_usage' => $materialData['material_usage'] ?? 0,
                    'material_result' => $materialData['material_result'] ?? null,
                    'combination_id' => $combinationId,
                    'created_at' => $now,
                    'updated_at' => $now,
                ]);

                $usage = (float) ($materialData['material_usage'] ?? 0);
                if ($price && $usage > 0) {
                    $stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
                }
            }

            foreach ($stockDecrementMap as $priceId => $totalUsage) {
                RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalUsage);
            }

            $costPerUnit = 0;
            if (($data['cutting_result'] ?? 0) > 0) {
                $costPerUnit = (int) ($totalMaterialCost / $data['cutting_result']);
            }

            $cutting->update([
                'description' => $data['description'] ?? null,
                'total_material_cost' => $totalMaterialCost,
                'cost_per_unit' => $costPerUnit,
            ]);

            CuttingResult::create([
                'cutting_id' => $cutting->id,
                'user_id' => auth()->id(),
                'product_name' => $data['product_name'] ?? null,
                'sample' => $data['sample'] ?? null,
                'original_outside_sample' => $data['original_outside_sample'] ?? null,
                'cutting_result' => $data['cutting_result'] ?? null,
                'created_at' => $now,
                'updated_at' => $now,
            ]);

            $this->syncPhotos($cutting, $data['photo_keys'] ?? [], 'images');

            return $cutting;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Cutting Diperbarui',
            body: 'Cutting berhasil diperbarui oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.cuttings.index', ['highlight' => $cutting->id]),
        );

        return $cutting;
    }

    public function destroy(Cutting $cutting): bool
    {
        $result = DB::transaction(function () use ($cutting) {
            $stockRestoreMap = $cutting->cuttingMaterials()
                ->select('raw_material_price_id', 'material_usage')
                ->where('material_usage', '>', 0)
                ->get()
                ->groupBy('raw_material_price_id')
                ->map(fn ($group) => $group->sum('material_usage'));

            foreach ($stockRestoreMap as $priceId => $totalUsage) {
                RawMaterialPrice::where('id', $priceId)->increment('stock', $totalUsage);
            }

            $cutting->cuttingResults()->delete();
            $cutting->cuttingMaterials()->delete();
            $cutting->cuttingMaterialCombinations()->delete();
            $cutting->delete();

            return true;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Cutting Dihapus',
            body: 'Cutting berhasil dihapus oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.cuttings.index'),
        );

        return $result;
    }

    public function complete(Cutting $cutting): Cutting
    {
        $cutting->update(['status' => CuttingStatus::COMPLETED]);

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Cutting Selesai',
            body: 'Cutting berhasil diselesaikan oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.cuttings.index', ['highlight' => $cutting->id]),
        );

        return $cutting;
    }
}
