<?php

namespace App\Services\Manage;

use App\Enums\StokOpnameStatus;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\StokOpname;
use App\Models\User;
use App\Services\System\PushNotificationService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

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

    public function paginateForIndex(array $tableQuery): LengthAwarePaginator
    {
        $query = StokOpname::query()
            ->with(['createdBy.profile', 'verifiedBy.profile'])
            ->withCount(['items', 'items as items_with_difference_count' => function (Builder $query): void {
                $query->where('difference', '!=', 0);
            }])
            ->when($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('createdBy.profile', function (Builder $query) use ($search): void {
                            $query->where('full_name', 'like', "%{$search}%");
                        });
                });
            });

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

        return $query
            ->paginate(25)
            ->withQueryString();
    }

    public function catalogItems(): array
    {
        return Product::query()
            ->active()
            ->with([
                'variants' => fn ($query) => $query
                    ->select('id', 'product_id', 'name', 'stock')
                    ->orderBy('created_at'),
            ])
            ->orderBy('name')
            ->get()
            ->map(fn (Product $product) => [
                'id' => $product->id,
                'name' => $product->name,
                'variants' => $product->variants->map(fn ($variant) => [
                    'id' => $variant->id,
                    'name' => $variant->name,
                    'stock' => $variant->stock,
                    'stock_formatted' => $variant->stock_formatted,
                ]),
            ])
            ->toArray();
    }

    public function findForEdit(StokOpname $stokOpname): array
    {
        $stokOpname->load(['items.productVariant.product', 'createdBy.profile']);

        return [
            'id' => $stokOpname->id,
            'opname_date' => $stokOpname->opname_date->format('Y-m-d'),
            'status' => $stokOpname->status->value,
            'status_label' => $stokOpname->status_label,
            'notes' => $stokOpname->notes,
            'verification_notes' => $stokOpname->verification_notes,
            'created_by_name' => $stokOpname->created_by_name,
            'created_at_formatted' => $stokOpname->created_at_formatted,
            'items' => $stokOpname->items->map(fn ($item) => [
                'id' => $item->id,
                'product_variant_id' => $item->product_variant_id,
                'variant_name' => $item->productVariant->name,
                'product_name' => $item->productVariant->product->name,
                'system_stock' => $item->system_stock,
                'physical_stock' => $item->physical_stock,
                'difference' => $item->difference,
                'notes' => $item->notes,
            ]),
        ];
    }

    public function create(array $validated, User $user): StokOpname
    {
        try {
            return DB::transaction(function () use ($validated, $user): StokOpname {
                $stokOpname = StokOpname::create([
                    'opname_date' => $validated['opname_date'],
                    'notes' => $validated['notes'],
                    'status' => StokOpnameStatus::DRAFT,
                    'created_by_id' => $user->id,
                ]);

                $this->syncItems($stokOpname, $validated['items'] ?? []);

                return $stokOpname;
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal membuat stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

    public function update(StokOpname $stokOpname, array $validated): void
    {
        if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname dengan status draft atau ditolak yang dapat diubah.',
            ]);
        }

        try {
            DB::transaction(function () use ($stokOpname, $validated): void {
                $stokOpname->update([
                    'opname_date' => $validated['opname_date'],
                    'notes' => $validated['notes'],
                    'status' => StokOpnameStatus::DRAFT,
                ]);

                $this->syncItems($stokOpname, $validated['items'] ?? []);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal memperbarui stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

    public function delete(StokOpname $stokOpname): void
    {
        if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname dengan status draft atau ditolak yang dapat dihapus.',
            ]);
        }

        try {
            DB::transaction(function () use ($stokOpname): void {
                $stokOpname->items()->delete();
                $stokOpname->delete();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menghapus stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

    public function submit(StokOpname $stokOpname, User $user): void
    {
        if ($stokOpname->status !== StokOpnameStatus::DRAFT && $stokOpname->status !== StokOpnameStatus::REJECTED) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname dengan status draft atau ditolak yang dapat diajukan.',
            ]);
        }

        if ($stokOpname->items()->count() === 0) {
            throw ValidationException::withMessages([
                'items' => 'Stok opname harus memiliki minimal 1 item.',
            ]);
        }

        try {
            DB::transaction(function () use ($stokOpname): void {
                $stokOpname->update([
                    'status' => StokOpnameStatus::PENDING,
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal mengajukan stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $this->pushNotificationService->sendToRoles(
            '📋 Stok Opname Menunggu Verifikasi',
            "Stok opname tanggal {$stokOpname->opname_date->format('d/m/Y')} oleh {$stokOpname->created_by_name} menunggu verifikasi.",
            ['owner', 'developer', 'admin-toko'],
            route('admin.manage.stok-opnames.index'),
        );
    }

    public function verify(StokOpname $stokOpname, User $user, ?string $verificationNotes = null): void
    {
        if ($stokOpname->status !== StokOpnameStatus::PENDING) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname yang menunggu verifikasi yang dapat diverifikasi.',
            ]);
        }

        try {
            DB::transaction(function () use ($stokOpname, $user, $verificationNotes): void {
                foreach ($stokOpname->items as $item) {
                    if ($item->difference !== 0) {
                        $item->productVariant()->update([
                            'stock' => $item->physical_stock,
                        ]);
                    }
                }

                $stokOpname->update([
                    'status' => StokOpnameStatus::VERIFIED,
                    'verified_by_id' => $user->id,
                    'verification_notes' => $verificationNotes,
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal memverifikasi stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $this->pushNotificationService->sendToUser(
            '✅ Stok Opname Terverifikasi',
            "Stok opname tanggal {$stokOpname->opname_date->format('d/m/Y')} telah diverifikasi dan stok telah disesuaikan.",
            $stokOpname->created_by_id,
            route('admin.manage.stok-opnames.index'),
        );
    }

    public function reject(StokOpname $stokOpname, User $user, string $reason): void
    {
        if ($stokOpname->status !== StokOpnameStatus::PENDING) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname yang menunggu verifikasi yang dapat ditolak.',
            ]);
        }

        try {
            DB::transaction(function () use ($stokOpname, $user, $reason): void {
                $stokOpname->update([
                    'status' => StokOpnameStatus::REJECTED,
                    'verified_by_id' => $user->id,
                    'verification_notes' => $reason,
                ]);
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menolak stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

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

        $this->pushNotificationService->sendToUser(
            '❌ Stok Opname Ditolak',
            "Stok opname tanggal {$stokOpname->opname_date->format('d/m/Y')} ditolak dengan alasan: {$reason}",
            $stokOpname->created_by_id,
            route('admin.manage.stok-opnames.index'),
        );
    }

    public function autoSave(array $validated, User $user): StokOpname
    {
        try {
            return DB::transaction(function () use ($validated, $user): StokOpname {
                $stokOpname = null;

                if (! empty($validated['stok_opname_id'])) {
                    $stokOpname = StokOpname::find($validated['stok_opname_id']);
                }

                if ($stokOpname && in_array($stokOpname->status, [StokOpnameStatus::DRAFT, StokOpnameStatus::REJECTED], true)) {
                    $stokOpname->update([
                        'opname_date' => $validated['opname_date'],
                        'notes' => $validated['notes'],
                        'status' => StokOpnameStatus::DRAFT,
                    ]);
                } else {
                    $stokOpname = StokOpname::create([
                        'opname_date' => $validated['opname_date'],
                        'notes' => $validated['notes'],
                        'status' => StokOpnameStatus::DRAFT,
                        'created_by_id' => $user->id,
                    ]);
                }

                $this->syncItems($stokOpname, $validated['items'] ?? []);

                return $stokOpname;
            });
        } catch (\Throwable $e) {
            Log::error('Gagal auto-save stok opname: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw $e;
        }
    }

    private function syncItems(StokOpname $stokOpname, array $items): void
    {
        $stokOpname->items()->delete();

        foreach ($items as $item) {
            $systemStock = ProductVariant::find($item['product_variant_id'])?->stock ?? 0;

            $stokOpname->items()->create([
                'product_variant_id' => $item['product_variant_id'],
                'system_stock' => $systemStock,
                'physical_stock' => $item['physical_stock'],
                'difference' => $item['physical_stock'] - $systemStock,
                'notes' => $item['notes'] ?? null,
            ]);
        }
    }

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

            return;
        }

        $query->latest();
    }
}
