<?php

namespace App\Services\Manage;

use App\Enums\OwnerVerificationAction;
use App\Enums\OwnerVerificationStatus;
use App\Enums\Permission;
use App\Enums\PriceType;
use App\Enums\ProductStockQuality;
use App\Models\OwnerVerificationRequest;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Restock;
use App\Models\RestockItem;
use App\Models\User;
use App\Services\Concerns\CachesQuery;
use App\Services\Concerns\RunsInTransaction;
use App\Services\Media\MediaService;
use App\Services\System\PushNotificationService;
use App\Support\Media\MediaPresenter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Validation\ValidationException;

class RestockService
{
    use CachesQuery, RunsInTransaction;

    private const MAX_PHOTOS = 1;

    public function __construct(
        private readonly MediaService $mediaService,
        private readonly PushNotificationService $pushNotificationService,
    ) {}

    public function paginateForIndex(array $tableQuery): LengthAwarePaginator
    {
        $query = Restock::query()
            ->with([
                'createdBy.profile',
                'pendingOwnerVerificationRequest.submittedBy.profile',
                'items.productVariant.product:id,name',
                'items.productVariant.media',
                'media',
            ])
            ->when(($tableQuery['search_id'] ?? '') !== '', function (Builder $query) use ($tableQuery): void {
                $query->where('restocks.id', $tableQuery['search_id']);
            })
            ->when(($tableQuery['search_id'] ?? '') === '' && $tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
                $search = $tableQuery['search'];
                $query->where(function (Builder $query) use ($search): void {
                    $query->where('notes', 'like', "%{$search}%")
                        ->orWhereHas('items.productVariant', function (Builder $query) use ($search): void {
                            $query->where('name', 'like', "%{$search}%")
                                ->orWhereHas('product', fn (Builder $query) => $query->where('name', 'like', "%{$search}%"));
                        });
                });
            });

        $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);

        return $query
            ->paginate(25)
            ->withQueryString()
            ->through(function (Restock $restock) {
                $restock->setAttribute(
                    'photos',
                    MediaPresenter::first($restock, 'photos'),
                );

                $pendingRequest = $restock->pendingOwnerVerificationRequest;

                $restock->setAttribute('has_pending_request', $pendingRequest !== null);
                $restock->setAttribute('pending_request_id', $pendingRequest?->id);
                $restock->setAttribute('pending_request_action', $pendingRequest?->action->value);
                $restock->setAttribute('pending_request_action_label', $pendingRequest?->action->label());
                $restock->setAttribute('pending_request_submitted_by_name', $pendingRequest?->submittedBy?->profile?->full_name ?? $pendingRequest?->submittedBy?->username);
                $restock->setAttribute('stock_type_label', $restock->stock_type->label());

                $restock->items->each(fn (RestockItem $item) => $this->breakItemCircularReference($item));

                return $restock;
            });
    }

    public function productCatalog(?Restock $restock = null, ?User $user = null): Collection
    {
        $selectedVariantIds = $restock
            ? $restock->items()->pluck('product_variant_id')->all()
            : ($user ? $this->draftItemsQuery($user)->pluck('product_variant_id')->all() : []);

        return Product::query()
            ->with([
                'variants' => fn ($query) => $query
                    ->with(['media', 'prices'])
                    ->orderBy('created_at'),
            ])
            ->where(function (Builder $query) use ($selectedVariantIds): void {
                $query->active();

                if ($selectedVariantIds !== []) {
                    $query->orWhereHas(
                        'variants',
                        fn (Builder $query) => $query->whereIn('id', $selectedVariantIds),
                    );
                }
            })
            ->orderBy('name')
            ->get()
            ->each(function (Product $product): void {
                $product->variants->each(function (ProductVariant $variant): void {
                    $variant->setAttribute(
                        'images',
                        MediaPresenter::collection($variant, 'images'),
                    );

                    $hargaModal = $variant->prices
                        ->firstWhere('type', PriceType::HARGA_MODAL);
                    $variant->setAttribute(
                        'harga_modal',
                        $hargaModal?->price ?? 0,
                    );
                    $variant->unsetRelation('prices');
                });
            });
    }

    public function findForEdit(Restock $restock): Restock
    {
        $restock->load([
            'items.productVariant.product:id,name',
            'items.productVariant.media',
            'media',
        ]);

        $restock->setAttribute(
            'photos',
            MediaPresenter::first($restock, 'photos'),
        );

        $restock->items->each(function (RestockItem $item): void {
            $variant = $item->productVariant;

            if ($variant) {
                $variant->setAttribute('images', MediaPresenter::collection($variant, 'images'));
            }

            $this->breakItemCircularReference($item);
        });

        return $restock;
    }

    public function draftItemsForUser(User $user): array
    {
        return $this->draftItemsQuery($user)
            ->with([
                'productVariant.product:id,name',
                'productVariant.media',
            ])
            ->get()
            ->map(function (RestockItem $item) {
                $result = $this->presentDraftItem($item);
                $this->breakItemCircularReference($item);

                return $result;
            })
            ->values()
            ->all();
    }

    public function clearDraftItemsForUser(User $user): void
    {
        $this->draftItemsQuery($user)->delete();
    }

    public function syncDraftItem(array $validated, User $user): array
    {
        $variant = ProductVariant::query()
            ->with('product:id,name')
            ->findOrFail($validated['product_variant_id']);

        $quantity = (float) $validated['quantity'];
        $unitPrice = (int) $validated['unit_price'];
        $subtotal = (int) round($quantity * $unitPrice);

        $item = RestockItem::query()->updateOrCreate(
            [
                'user_id' => $user->id,
                'product_variant_id' => $variant->id,
                'restock_id' => null,
            ],
            [
                'quantity' => $quantity,
                'unit_price' => $unitPrice,
                'subtotal' => $subtotal,
            ],
        );

        $item->load([
            'productVariant.product:id,name',
            'productVariant.media',
        ]);

        $result = $this->presentDraftItem($item);
        $this->breakItemCircularReference($item);

        return $result;
    }

    public function removeDraftItem(User $user, ProductVariant $productVariant): void
    {
        RestockItem::query()
            ->whereNull('restock_id')
            ->where('user_id', $user->id)
            ->where('product_variant_id', $productVariant->id)
            ->delete();
    }

    public function create(array $validated, User $user): Restock
    {
        $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value);

        $restock = $this->runInTransaction(
            function () use ($validated, $user, $isOwner): Restock {
                $resolvedItems = $this->processRequestItems($validated['items'] ?? []);

                $subtotal = array_sum(array_column($resolvedItems, 'subtotal'));

                $restock = Restock::create([
                    'created_by_id' => $user->id,
                    'subtotal' => $subtotal,
                    'total' => $subtotal,
                    'notes' => $validated['notes'] ?? null,
                    'stock_type' => $validated['stock_type'] ?? 'good',
                ]);

                foreach ($resolvedItems as $itemData) {
                    $restock->items()->create([
                        'product_variant_id' => $itemData['product_variant_id'],
                        'quantity' => $itemData['quantity'],
                        'unit_price' => $itemData['unit_price'],
                        'subtotal' => $itemData['subtotal'],
                    ]);
                }

                $this->syncPhotos($restock, $validated);

                $restock->load(['items.productVariant.product:id,name']);

                $stockType = $restock->stock_type;

                if ($isOwner) {
                    foreach ($restock->items as $item) {
                        $this->incrementStock($item, $stockType);
                    }
                } else {
                    OwnerVerificationRequest::create([
                        'action' => OwnerVerificationAction::CREATE,
                        'status' => OwnerVerificationStatus::PENDING,
                        'subject_type' => Restock::class,
                        'subject_id' => $restock->id,
                        'submitted_by_id' => $user->id,
                        'payload' => [
                            'old' => null,
                            'new' => $this->snapshotRestock($restock),
                        ],
                    ]);
                }

                return $restock;
            },
            'Gagal membuat restock',
        );

        if (! $isOwner) {
            $this->notifyForPendingRequest(
                $user,
                'Tambah Restock',
                "Pengajuan restock senilai {$restock->total_formatted} oleh {$user->profile?->full_name} menunggu verifikasi owner.",
                route('admin.manage.restocks.index', ['search_id' => $restock->id]),
                (string) $restock->id,
            );
        }

        $this->cacheForgetByPattern('manage:restocks:*');

        return $restock;
    }

    public function update(Restock $restock, array $validated, User $user): void
    {
        $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value);
        $restock->load(['items.productVariant.product:id,name']);

        $this->runInTransaction(
            function () use ($restock, $validated, $user, $isOwner): void {
                if ($isOwner) {
                    $payload = $this->buildPayloadFromValidated($validated);
                    $this->applyPayloadToRestock($restock, $payload);

                    if (($validated['photos'] ?? null) !== null || ($validated['remove_media_ids'] ?? []) !== []) {
                        $this->syncPhotos($restock, $validated);
                    }
                } else {
                    $verificationRequest = OwnerVerificationRequest::create([
                        'action' => OwnerVerificationAction::UPDATE,
                        'status' => OwnerVerificationStatus::PENDING,
                        'subject_type' => Restock::class,
                        'subject_id' => $restock->id,
                        'submitted_by_id' => $user->id,
                        'payload' => [
                            'old' => $this->snapshotRestock($restock),
                            'new' => $this->buildPayloadFromValidated($validated),
                        ],
                    ]);

                    if (($validated['photos'] ?? null) !== null || ($validated['remove_media_ids'] ?? []) !== []) {
                        $this->syncRequestPhotos($verificationRequest, $validated);
                    }
                }
            },
            'Gagal memperbarui restock',
        );

        if (! $isOwner) {
            $this->notifyForPendingRequest(
                $user,
                'Ubah Restock',
                "Pengajuan ubah restock oleh {$user->profile?->full_name} menunggu verifikasi owner.",
                route('admin.manage.restocks.index', ['search_id' => $restock->id]),
                (string) $restock->id,
            );
        }

        $this->cacheForgetByPattern('manage:restocks:*');
    }

    public function delete(Restock $restock, User $user): void
    {
        $isOwner = $user->can(Permission::OWNER_VERIFICATIONS_VERIFY->value);

        if ($isOwner) {
            $this->executeDelete($restock);
            $this->cacheForgetByPattern('manage:restocks:*');

            return;
        }

        $restock->load(['items.productVariant.product:id,name']);

        $this->runInTransaction(
            function () use ($restock, $user): void {
                OwnerVerificationRequest::create([
                    'action' => OwnerVerificationAction::DELETE,
                    'status' => OwnerVerificationStatus::PENDING,
                    'subject_type' => Restock::class,
                    'subject_id' => $restock->id,
                    'submitted_by_id' => $user->id,
                    'payload' => [
                        'old' => $this->snapshotRestock($restock),
                        'new' => null,
                    ],
                ]);
            },
            'Gagal mengajukan penghapusan restock',
        );

        $this->notifyForPendingRequest(
            $user,
            'Hapus Restock',
            "Pengajuan hapus restock oleh {$user->profile?->full_name} menunggu verifikasi owner.",
            route('admin.manage.restocks.index', ['search_id' => $restock->id]),
            (string) $restock->id,
        );
    }

    public function applyVerificationRequest(OwnerVerificationRequest $verificationRequest): void
    {
        match ($verificationRequest->action) {
            OwnerVerificationAction::CREATE => $this->applyCreate($verificationRequest),
            OwnerVerificationAction::UPDATE => $this->applyUpdate($verificationRequest),
            OwnerVerificationAction::DELETE => $this->applyDelete($verificationRequest),
            default => throw ValidationException::withMessages([
                'action' => 'Aksi verifikasi restock tidak didukung.',
            ]),
        };

        $this->cacheForgetByPattern('manage:restocks:*');
    }

    public function rejectVerificationRequest(OwnerVerificationRequest $verificationRequest): void
    {
        match ($verificationRequest->action) {
            OwnerVerificationAction::CREATE => $this->rejectCreate($verificationRequest),
            OwnerVerificationAction::UPDATE, OwnerVerificationAction::DELETE => null,
            default => throw ValidationException::withMessages([
                'action' => 'Aksi verifikasi restock tidak didukung.',
            ]),
        };

        $this->cacheForgetByPattern('manage:restocks:*');
    }

    public function clearVerificationRequestMedia(OwnerVerificationRequest $verificationRequest): void
    {
        $verificationRequest->clearMediaCollection('photos');
    }

    public function applyCreate(OwnerVerificationRequest $verificationRequest): void
    {
        $restock = $verificationRequest->subject;

        if (! $restock instanceof Restock) {
            throw ValidationException::withMessages([
                'restock' => 'Restock tidak ditemukan.',
            ]);
        }

        $restock->load('items');

        $stockType = $restock->stock_type;

        foreach ($restock->items as $item) {
            $this->incrementStock($item, $stockType);
        }
    }

    public function applyUpdate(OwnerVerificationRequest $verificationRequest): void
    {
        $restock = $verificationRequest->subject;

        if (! $restock instanceof Restock) {
            throw ValidationException::withMessages([
                'restock' => 'Restock tidak ditemukan.',
            ]);
        }

        $this->applyPayloadToRestock($restock, $this->payloadNew($verificationRequest), $verificationRequest);
    }

    public function applyDelete(OwnerVerificationRequest $verificationRequest): void
    {
        $restock = $verificationRequest->subject;

        if (! $restock instanceof Restock) {
            throw ValidationException::withMessages([
                'restock' => 'Restock tidak ditemukan.',
            ]);
        }

        $this->executeDelete($restock);
    }

    private function processRequestItems(array $items): array
    {
        $resolvedItems = [];

        foreach ($items as $index => $itemData) {
            $variant = ProductVariant::query()->find($itemData['product_variant_id']);

            if ($variant === null) {
                throw ValidationException::withMessages([
                    "items.{$index}.product_variant_id" => 'Varian produk tidak ditemukan.',
                ]);
            }

            $quantity = (float) $itemData['quantity'];
            $unitPrice = (int) $itemData['unit_price'];
            $lineSubtotal = (int) round($quantity * $unitPrice);

            $resolvedItems[] = [
                'product_variant_id' => $variant->id,
                'quantity' => $quantity,
                'unit_price' => $unitPrice,
                'subtotal' => $lineSubtotal,
            ];
        }

        return $resolvedItems;
    }

    private function syncPhotos(Restock $restock, array $validated): void
    {
        $this->mediaService->syncCollection(
            $restock,
            'photos',
            $validated['photos'] ?? null,
            $validated['remove_media_ids'] ?? null,
            self::MAX_PHOTOS,
            required: false,
            errorKey: 'photos',
            s3Keys: $validated['s3_keys'] ?? null,
        );
    }

    private function draftItemsQuery(User $user): Builder
    {
        return RestockItem::query()
            ->whereNull('restock_id')
            ->where('user_id', $user->id);
    }

    private function presentDraftItem(RestockItem $item): array
    {
        $variant = $item->productVariant;
        $product = $variant?->product;

        return [
            'product_variant_id' => $item->product_variant_id,
            'product_name' => $product?->name ?? '',
            'variant_name' => $variant?->name ?? '',
            'stock' => $variant?->stock ?? 0,
            'quantity' => $item->quantity_input,
            'unit_price' => $item->unit_price,
            'images' => $variant ? MediaPresenter::collection($variant, 'images') : [],
        ];
    }

    private function stockColumn(ProductStockQuality $stockType): string
    {
        return match ($stockType) {
            ProductStockQuality::GOOD => 'stock',
            ProductStockQuality::REJECT => 'reject_stock',
            ProductStockQuality::RETAIL => 'retail_stock',
        };
    }

    private function incrementStock(RestockItem $item, ProductStockQuality $stockType): void
    {
        ProductVariant::query()
            ->whereKey($item->product_variant_id)
            ->increment($this->stockColumn($stockType), $item->quantity);
    }

    private function decrementStock(RestockItem $item, ProductStockQuality $stockType): void
    {
        ProductVariant::query()
            ->whereKey($item->product_variant_id)
            ->decrement($this->stockColumn($stockType), $item->quantity);
    }

    private function notifyForPendingRequest(User $user, string $typeLabel, string $body, string $submitterUrl, ?string $search = null): void
    {
        $ownerUrl = route('admin.manage.restocks.index');
        if ($search !== null) {
            $ownerUrl = route('admin.manage.restocks.index', ['search_id' => $search]);
        }

        $this->pushNotificationService->sendToRoles(
            "📦 {$typeLabel} Menunggu Persetujuan Owner",
            $body,
            ['owner', 'developer'],
            $ownerUrl,
        );

        $this->pushNotificationService->sendToUser(
            '📤 Pengajuan Terkirim',
            $body,
            $user->id,
            $submitterUrl,
        );
    }

    private function rejectCreate(OwnerVerificationRequest $verificationRequest): void
    {
        $restock = $verificationRequest->subject;

        if (! $restock instanceof Restock) {
            return;
        }

        $this->runInTransaction(
            function () use ($restock): void {
                $restock->items()->delete();
                $restock->delete();
            },
            'Gagal menolak restock',
        );
    }

    private function executeDelete(Restock $restock): void
    {
        $this->runInTransaction(
            function () use ($restock): void {
                $restock->load('items');

                $stockType = $restock->stock_type;

                foreach ($restock->items as $item) {
                    $this->decrementStock($item, $stockType);
                }

                $restock->items()->delete();
                $restock->delete();
            },
            'Gagal menghapus restock',
        );
    }

    private function applyPayloadToRestock(
        Restock $restock,
        array $payload,
        ?OwnerVerificationRequest $verificationRequest = null,
    ): void {
        $this->runInTransaction(
            function () use ($restock, $payload, $verificationRequest): void {
                $restock->load('items');

                $stockType = $restock->stock_type;

                foreach ($restock->items as $item) {
                    $this->decrementStock($item, $stockType);
                }

                $restock->items()->delete();

                foreach ($payload['items'] ?? [] as $itemData) {
                    $restockItem = $restock->items()->create([
                        'product_variant_id' => $itemData['product_variant_id'],
                        'quantity' => $itemData['quantity'],
                        'unit_price' => $itemData['unit_price'],
                        'subtotal' => $itemData['subtotal'],
                    ]);
                    $this->incrementStock($restockItem, $stockType);
                }

                $restock->update([
                    'subtotal' => $payload['subtotal'],
                    'total' => $payload['total'],
                    'notes' => $payload['notes'] ?? null,
                    'stock_type' => $payload['stock_type'] ?? $restock->stock_type,
                ]);

                if ($verificationRequest !== null) {
                    $this->applyRequestPhotos($verificationRequest, $restock, $payload);
                }
            },
            'Gagal memperbarui restock',
        );
    }

    private function snapshotRestock(Restock $restock): array
    {
        $restock->load([
            'items.productVariant.product:id,name',
        ]);

        return [
            'subtotal' => $restock->subtotal,
            'total' => $restock->total,
            'notes' => $restock->notes,
            'stock_type' => $restock->stock_type->value,
            'items' => $restock->items
                ->map(fn (RestockItem $item) => [
                    'product_variant_id' => $item->product_variant_id,
                    'product_name' => $item->productVariant?->product?->name,
                    'variant_name' => $item->productVariant?->name,
                    'quantity' => (float) $item->quantity,
                    'unit_price' => $item->unit_price,
                    'subtotal' => $item->subtotal,
                ])
                ->all(),
            'has_photos' => $restock->hasMedia('photos'),
        ];
    }

    private function buildPayloadFromValidated(array $validated): array
    {
        $lineItems = $this->enrichLineItems($this->processRequestItems($validated['items']));
        $subtotal = array_sum(array_column($lineItems, 'subtotal'));

        return [
            'subtotal' => $subtotal,
            'total' => $subtotal,
            'notes' => $validated['notes'] ?? null,
            'stock_type' => $validated['stock_type'] ?? 'good',
            'items' => $lineItems,
            'remove_media_ids' => $validated['remove_media_ids'] ?? [],
        ];
    }

    private function enrichLineItems(array $lineItems): array
    {
        $variants = ProductVariant::query()
            ->with('product:id,name')
            ->whereIn('id', array_column($lineItems, 'product_variant_id'))
            ->get()
            ->keyBy('id');

        return collect($lineItems)
            ->map(function (array $item) use ($variants) {
                $variant = $variants->get($item['product_variant_id']);

                return array_merge($item, [
                    'product_name' => $variant?->product?->name,
                    'variant_name' => $variant?->name,
                ]);
            })
            ->all();
    }

    private function syncRequestPhotos(OwnerVerificationRequest $verificationRequest, array $validated): void
    {
        $this->mediaService->syncCollection(
            $verificationRequest,
            'photos',
            $validated['photos'] ?? null,
            $validated['remove_media_ids'] ?? null,
            self::MAX_PHOTOS,
            required: false,
            errorKey: 'photos',
            s3Keys: $validated['s3_keys'] ?? null,
        );
    }

    private function applyRequestPhotos(
        OwnerVerificationRequest $verificationRequest,
        Restock $restock,
        array $payload,
    ): void {
        if ($verificationRequest->hasMedia('photos')) {
            $restock->clearMediaCollection('photos');

            foreach ($verificationRequest->getMedia('photos') as $media) {
                $media->copy($restock, 'photos');
            }

            return;
        }

        foreach ($payload['remove_media_ids'] ?? [] as $mediaId) {
            $restock->deleteMedia((int) $mediaId);
        }
    }

    private function payloadNew(OwnerVerificationRequest $verificationRequest): array
    {
        $payload = $verificationRequest->payload ?? [];

        if (array_key_exists('new', $payload)) {
            return is_array($payload['new']) ? $payload['new'] : [];
        }

        return is_array($payload) ? $payload : [];
    }

    private function breakItemCircularReference(RestockItem $item): void
    {
        $variant = $item->productVariant;

        if ($variant) {
            $product = $variant->product;

            $item->setAttribute('variant_name', $variant->name);
            $item->setAttribute('stock', $variant->stock ?? 0);

            if ($product) {
                $item->setAttribute('product_id', $product->id);
                $item->setAttribute('product_name', $product->name);
            }

            $variant->unsetRelation('product');
        }

        $item->unsetRelation('productVariant');
    }

    private function applySorting(Builder $query, string $sort, string $direction): void
    {
        if (in_array($sort, ['created_at', 'total', 'subtotal'], true)) {
            $query->orderBy($sort, $direction);

            return;
        }

        $query->latest();
    }
}
