<?php

namespace App\Services\Admin\Manage;

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\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 = []): LengthAwarePaginator
    {
        $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',
                'cuttingResults:id,cutting_id,product_name,cutting_result',
                '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',
            ])
            ->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->getFirstMedia('images');
            $cutting->photo_url = $cuttingMedia
                ? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
                : null;
            $cutting->photo_conversion_url = $cuttingMedia
                ? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath('thumb'))
                : null;

            $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
                        ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                        : null;
                }
            });
        });

        return $paginator;
    }

    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->getFirstMedia('images');
        $cutting->photo_url = $cuttingMedia
            ? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
            : null;
        $cutting->photo_conversion_url = $cuttingMedia
            ? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath('thumb'))
            : null;

        $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
                    ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                    : 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_url' => $cutting->photo_url,
            'photo_conversion_url' => $cutting->photo_conversion_url,
            '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->getFirstMedia('images');
        $photoKey = $cuttingMedia?->getCustomProperty('s3_key') ?? $cuttingMedia?->file_name;
        $photoUrl = $cuttingMedia
            ? $this->s3Service->getTemporaryUrl($cuttingMedia->getPath())
            : null;

        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_key' => $photoKey,
            'photo_url' => $photoUrl,
        ];
    }

    public function store(array $data): Cutting
    {
        return DB::transaction(function () use ($data) {
            foreach ($data['materials'] as $materialData) {
                $usage = (int) ($materialData['material_usage'] ?? 0);
                if ($usage <= 0) {
                    continue;
                }
                $price = RawMaterialPrice::find($materialData['raw_material_price_id']);
                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;
            }

            foreach ($data['materials'] as $materialData) {
                $price = RawMaterialPrice::find($materialData['raw_material_price_id']);
                $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,
                ]);

                if ($price && ($materialData['material_usage'] ?? 0) > 0) {
                    $price->decrement('stock', (int) $materialData['material_usage']);
                }
            }

            $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_key'])) {
                $this->registerMedia(
                    model: $cutting,
                    s3Key: $data['photo_key'],
                    collectionName: 'images',
                    orderColumn: 1,
                );
            }

            return $cutting;
        });
    }

    public function update(Cutting $cutting, array $data): Cutting
    {
        return DB::transaction(function () use ($cutting, $data) {
            $cutting->load(['cuttingMaterials.rawMaterialPrice']);

            foreach ($cutting->cuttingMaterials as $oldMaterial) {
                if ($oldMaterial->material_usage > 0 && $oldMaterial->rawMaterialPrice) {
                    $oldMaterial->rawMaterialPrice->increment('stock', (int) $oldMaterial->material_usage);
                }
            }

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

            $cutting->load(['cuttingMaterials', 'cuttingMaterialCombinations', 'cuttingResults']);

            $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;
            }

            foreach ($data['materials'] as $materialData) {
                $price = RawMaterialPrice::find($materialData['raw_material_price_id']);
                $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,
                ]);

                if ($price && ($materialData['material_usage'] ?? 0) > 0) {
                    $price->decrement('stock', (int) $materialData['material_usage']);
                }
            }

            $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->syncPhoto($cutting, $data);

            return $cutting;
        });
    }

    public function destroy(Cutting $cutting): bool
    {
        return DB::transaction(function () use ($cutting) {
            $cutting->load('cuttingMaterials.rawMaterialPrice');

            foreach ($cutting->cuttingMaterials as $material) {
                if ($material->material_usage > 0 && $material->rawMaterialPrice) {
                    $material->rawMaterialPrice->increment('stock', (int) $material->material_usage);
                }
            }

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

            return true;
        });
    }
}
