<?php

namespace App\Services\Manage;

use App\Enums\CuttingStatus;
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingResultPrice;
use App\Models\ProductPrice;
use App\Models\ProductVariant;
use App\Models\RawMaterialPrice;
use App\Models\User;
use App\Services\System\PushNotificationService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

class StockService
{
    public function __construct(
        private readonly PushNotificationService $pushNotificationService,
    ) {}

    public function getPendingVerificationCuttings(User $user): Collection
    {
        return Cutting::query()
            ->with([
                'createdBy.profile',
                'rejection.rejectedBy.profile',
                'materials.rawMaterialPrice.rawMaterial:id,name,unit',
                'results.productVariant.product:id,name',
                'results.productVariant:id,product_id,name',
            ])
            ->completed()
            ->latest()
            ->get()
            ->each(function (Cutting $cutting): void {
                $this->appendCostPreview($cutting);
                $cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
            });
    }

    public function getPendingApprovalCuttings(User $user): Collection
    {
        return Cutting::query()
            ->with([
                'createdBy.profile',
                'submittedBy.profile',
                'rejection.rejectedBy.profile',
                'materials.rawMaterialPrice.rawMaterial:id,name,unit',
                'results.productVariant.product:id,name',
                'results.productVariant:id,product_id,name',
                'resultPrices.productVariant:id,product_id,name',
            ])
            ->pendingVerification()
            ->latest()
            ->get()
            ->each(function (Cutting $cutting): void {
                $this->appendCostPreview($cutting);
                $cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
            });
    }

    public function submitVerification(
        Cutting $cutting,
        User $user,
        ?string $verificationNote = null,
        ?array $results = null,
        ?array $resultPrices = null,
    ): void {
        if ($cutting->status !== CuttingStatus::COMPLETED) {
            throw ValidationException::withMessages([
                'status' => 'Hanya cutting yang sudah selesai yang dapat diverifikasi.',
            ]);
        }

        try {
            DB::transaction(function () use ($cutting, $user, $verificationNote, $results, $resultPrices): void {
                $cutting->load(['materials.rawMaterialPrice', 'results']);

                if ($results !== null) {
                    foreach ($results as $item) {
                        $cutting->results()
                            ->where('product_variant_id', $item['product_variant_id'])
                            ->update([
                                'sample' => $item['good'],
                                'original_outside_sample' => $item['reject'],
                            ]);
                    }
                    $cutting->load('results');
                }

                $this->storeResultPrices($cutting, $resultPrices ?? []);

                if ($verificationNote !== null && trim($verificationNote) !== '') {
                    $cutting->rejection()->create([
                        'reason' => trim($verificationNote),
                        'rejected_by_id' => $user->id,
                    ]);
                }

                $cutting->submitted_by_id = $user->id;
                $cutting->status = CuttingStatus::PENDING_VERIFICATION;
                $cutting->save();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal mengajukan verifikasi stok: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $description = $cutting->description ?? '-';

        $this->pushNotificationService->sendToRoles(
            '📦 Verifikasi Stok Menunggu Persetujuan',
            "Cutting dengan deskripsi '{$description}' telah diajukan verifikasi dan menunggu persetujuan owner.",
            ['owner', 'developer'],
            route('admin.manage.stocks.index'),
        );
    }

    public function approveVerification(
        Cutting $cutting,
        User $user,
        ?string $approvalNote = null,
    ): void {
        if ($cutting->status !== CuttingStatus::PENDING_VERIFICATION) {
            throw ValidationException::withMessages([
                'status' => 'Hanya cutting yang menunggu verifikasi yang dapat disetujui.',
            ]);
        }

        try {
            DB::transaction(function () use ($cutting, $user, $approvalNote): void {
                $cutting->load(['materials.rawMaterialPrice', 'results', 'resultPrices']);

                $this->applyProductStockOnVerify($cutting);
                $this->applyResultPricesToProducts($cutting);

                if ($approvalNote !== null && trim($approvalNote) !== '') {
                    $cutting->rejection()->create([
                        'reason' => trim($approvalNote),
                        'rejected_by_id' => $user->id,
                    ]);
                }

                $cutting->status = CuttingStatus::VERIFIED;
                $cutting->save();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menyetujui verifikasi stok: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $description = $cutting->description ?? '-';

        $this->pushNotificationService->sendToRoles(
            '📦 Stok Cutting Diverifikasi',
            "Cutting dengan deskripsi '{$description}' telah disetujui owner dan stok produk telah ditambahkan ke toko.",
            ['owner', 'developer'],
            route('admin.manage.stocks.index'),
        );
    }

    public function rejectVerification(
        Cutting $cutting,
        User $user,
        string $reason,
    ): void {
        if ($cutting->status !== CuttingStatus::PENDING_VERIFICATION) {
            throw ValidationException::withMessages([
                'status' => 'Hanya cutting yang menunggu verifikasi yang dapat ditolak.',
            ]);
        }

        try {
            DB::transaction(function () use ($cutting, $user, $reason): void {
                $cutting->rejection()->create([
                    'reason' => trim($reason),
                    'rejected_by_id' => $user->id,
                ]);

                $cutting->status = CuttingStatus::COMPLETED;
                $cutting->save();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menolak verifikasi stok: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $description = $cutting->description ?? '-';
        $this->pushNotificationService->sendToRoles(
            '📦 Verifikasi Cutting Ditolak Owner',
            "Cutting dengan deskripsi '{$description}' ditolak oleh owner dengan alasan: '{$reason}'.",
            ['owner', 'developer'],
            route('admin.manage.stocks.index'),
        );

        if ($cutting->submitted_by_id) {
            $this->pushNotificationService->sendToUser(
                '📦 Verifikasi Cutting Ditolak Owner',
                "Cutting dengan deskripsi '{$description}' yang Anda verifikasi ditolak oleh owner dengan alasan: '{$reason}'.",
                $cutting->submitted_by_id,
                route('admin.manage.stocks.index'),
            );
        }
    }

    private function breakMaterialCircularReference(CuttingMaterial $material): void
    {
        $price = $material->rawMaterialPrice;

        if ($price) {
            $rawMaterial = $price->rawMaterial;

            if ($rawMaterial) {
                $unitAbbreviation = $rawMaterial->unit->abbreviation();
                $price->setAttribute('unit_abbreviation', $unitAbbreviation);
                $material->setAttribute('unit_abbreviation', $unitAbbreviation);
            }

            $price->unsetRelation('rawMaterial');
        }

        $material->unsetRelation('rawMaterialPrice');
    }

    private function applyProductStockOnVerify(Cutting $cutting): void
    {
        foreach ($cutting->results as $result) {
            if ($result->sample > 0) {
                ProductVariant::query()
                    ->whereKey($result->product_variant_id)
                    ->increment('stock', $result->sample);
            }

            if ($result->original_outside_sample > 0) {
                ProductVariant::query()
                    ->whereKey($result->product_variant_id)
                    ->increment('reject_stock', $result->original_outside_sample);
            }
        }
    }

    private function storeResultPrices(Cutting $cutting, array $resultPrices): void
    {
        // Extract harga_modal from the first variant's prices and set as cost_per_unit
        $costPerUnit = 0;
        foreach ($resultPrices as $resultData) {
            foreach ($resultData['prices'] as $priceData) {
                if ($priceData['type'] === 'harga_modal' && (int) $priceData['price'] > 0) {
                    $costPerUnit = (int) $priceData['price'];
                    break 2;
                }
            }
        }

        if ($costPerUnit > 0) {
            $cutting->cost_per_unit = $costPerUnit;
        } else {
            $costPerUnit = (int) ($cutting->cost_per_unit ?? 0);
        }

        foreach ($resultPrices as $resultData) {
            foreach ($resultData['prices'] as $priceData) {
                if ((int) $priceData['price'] > 0) {
                    // Store/update to cutting_result_prices
                    CuttingResultPrice::query()->updateOrCreate(
                        [
                            'cutting_id' => $cutting->id,
                            'product_variant_id' => $resultData['product_variant_id'],
                            'price_type' => $priceData['type'],
                        ],
                        [
                            'price' => (int) $priceData['price'],
                            'cost_per_unit' => $costPerUnit,
                        ],
                    );
                }
            }
        }
    }

    private function applyResultPricesToProducts(Cutting $cutting): void
    {
        $resultPrices = $cutting->resultPrices()->with('productVariant')->get();

        foreach ($resultPrices as $resultPrice) {
            if ($resultPrice->price > 0) {
                ProductPrice::query()->updateOrCreate(
                    [
                        'variant_id' => $resultPrice->product_variant_id,
                        'type' => $resultPrice->price_type->value,
                    ],
                    [
                        'price' => $resultPrice->price,
                    ],
                );
            }
        }
    }

    private function appendCostPreview(Cutting $cutting): void
    {
        $cutting->setAttribute('total_result_pieces', (int) $cutting->results->sum('cutting_result'));
        $cutting->setAttribute('total_material_usage', (float) $cutting->materials->sum('material_usage'));

        $totalMaterialCost = $cutting->total_material_cost ?? 0;
        $sewingCost = (int) ($cutting->sewing_cost ?? 0);
        $otherCost = (int) ($cutting->other_cost ?? 0);
        $totalProductionCost = $totalMaterialCost + $sewingCost + $otherCost;
        $costPerUnit = $cutting->cost_per_unit ?? 0;

        $cutting->setAttribute('total_material_cost', $totalMaterialCost);
        $cutting->setAttribute('total_material_cost_formatted', 'Rp '.number_format($totalMaterialCost, 0, ',', '.'));
        $cutting->setAttribute('sewing_cost', $sewingCost);
        $cutting->setAttribute('sewing_cost_formatted', 'Rp '.number_format($sewingCost, 0, ',', '.'));
        $cutting->setAttribute('other_cost', $otherCost);
        $cutting->setAttribute('other_cost_formatted', 'Rp '.number_format($otherCost, 0, ',', '.'));
        $cutting->setAttribute('total_production_cost', $totalProductionCost);
        $cutting->setAttribute('total_production_cost_formatted', 'Rp '.number_format($totalProductionCost, 0, ',', '.'));
        $cutting->setAttribute('estimated_cost_per_unit', $costPerUnit);
        $cutting->setAttribute('estimated_cost_per_unit_formatted', 'Rp '.number_format($costPerUnit, 0, ',', '.'));
    }
}
