<?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\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Services\Concerns\RegistersMedia;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class CuttingService
{
    use RegistersMedia;

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

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): 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,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,price,stock',
                '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}%");
            })
            ->orderBy($sort, $direction)
            ->paginate($perPage);

        $paginator->getCollection()->each(function (Cutting $cutting) {
            $cuttingMedia = $cutting->getFirstMedia('photos');
            $cutting->photo_url = $cuttingMedia
                ? $this->s3Service->getTemporaryUrl($cuttingMedia->file_name)
                : null;

            $cutting->cuttingMaterials->each(function (CuttingMaterial $material) {
                $media = $material->rawMaterialPrice?->getFirstMedia('photos');
                if ($material->rawMaterialPrice) {
                    $material->rawMaterialPrice->photo_url = $media
                        ? $this->s3Service->getTemporaryUrl($media->file_name)
                        : null;
                }
            });
        });

        return $paginator;
    }

    public function getForCreate(): array
    {
        return [
            'rawMaterials' => RawMaterial::query()
                ->select('id', 'name', 'unit', 'is_active')
                ->with([
                    'rawMaterialPrices:id,raw_material_id,variant,price,stock',
                ])
                ->orderBy('name')
                ->get()
                ->each(function (RawMaterial $rawMaterial) {
                    $rawMaterial->rawMaterialPrices->each(function (RawMaterialPrice $price) {
                        $media = $price->getFirstMedia('photos');
                        $price->photo_url = $media
                            ? $this->s3Service->getTemporaryUrl($media->file_name)
                            : null;
                    });
                }),
        ];
    }

    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('photos');

            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->file_name)
                    : 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('photos');
        $photoKey = $cuttingMedia?->file_name;
        $photoUrl = $cuttingMedia
            ? $this->s3Service->getTemporaryUrl($cuttingMedia->file_name)
            : 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 create(array $data): Cutting
    {
        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}.",
                ]);
            }
        }

        return DB::transaction(function () use ($data) {
            $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: 'photos',
                    orderColumn: 1,
                );
            }

            return $cutting;
        });
    }

    public function update(Cutting $cutting, array $data): Cutting
    {
        $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}.",
                ]);
            }
        }

        return DB::transaction(function () use ($cutting, $data) {
            $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 delete(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->clearMediaCollection('photos');
            $cutting->cuttingResults()->delete();
            $cutting->cuttingMaterials()->delete();
            $cutting->cuttingMaterialCombinations()->delete();
            $cutting->delete();

            return true;
        });
    }
}
