<?php

namespace App\Services\Admin\Manage;

use App\Enums\ProductStockQuality;
use App\Enums\Role;
use App\Enums\StokOpnameStatus;
use App\Models\ProductVariant;
use App\Models\StokOpname;
use App\Models\StokOpnameItem;
use App\Services\Concerns\HasStockAdjustment;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class StokOpnameService
{
    use HasStockAdjustment;

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

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $highlight = null, ?string $status = null): LengthAwarePaginator
    {
        $itemsCountQuery = '(SELECT COUNT(DISTINCT product_variant_id) FROM stok_opname_items WHERE stok_opname_items.stok_opname_id = stok_opnames.id)';
        $totalDifferenceQuery = '(SELECT IFNULL(SUM(difference), 0) FROM stok_opname_items WHERE stok_opname_items.stok_opname_id = stok_opnames.id)';
        $productNamesQuery = '(SELECT GROUP_CONCAT(DISTINCT p.name ORDER BY p.name SEPARATOR \', \') FROM stok_opname_items soi JOIN product_variants pv ON pv.id = soi.product_variant_id JOIN products p ON p.id = pv.product_id WHERE soi.stok_opname_id = stok_opnames.id)';

        return StokOpname::query()
            ->select(['id', 'created_by_id', 'verified_by_id', 'opname_date', 'status', 'notes', 'verification_notes', 'created_at'])
            ->with([
                'createdBy:id',
                'createdBy.userProfile:id,user_id,full_name',
                'verifiedBy:id',
                'verifiedBy.userProfile:id,user_id,full_name',
            ])
            ->selectRaw("{$itemsCountQuery} as items_count")
            ->selectRaw("{$totalDifferenceQuery} as total_difference")
            ->selectRaw("{$productNamesQuery} as product_names")
            ->when($highlight, fn ($q) => $q->where('id', $highlight))
            ->when($status, fn ($q) => $q->where('status', $status))
            ->when($search, function ($q) use ($search) {
                $q->whereHas('stokOpnameItems.productVariant.product', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
                    ->orWhere('notes', 'like', "%{$search}%");
            })
            ->orderBy($sort, $direction)
            ->paginate($perPage);
    }

    public function store(array $data): StokOpname
    {
        return DB::transaction(function () use ($data) {
            $stokOpname = StokOpname::create([
                'created_by_id' => auth()->id(),
                'opname_date' => $data['opname_date'],
                'status' => StokOpnameStatus::DRAFT,
                'notes' => $data['notes'] ?? null,
            ]);

            $this->createItems($stokOpname, $data['items']);

            NotificationService::notify(
                roles: [Role::OWNER, Role::DEVELOPER],
                title: 'Stok Opname Baru',
                body: 'Stok opname baru berhasil dibuat oleh '.auth()->user()->full_name.'.',
                url: route('admin.manage.stok-opnames.index', ['highlight' => $stokOpname->id]),
            );

            return $stokOpname;
        });
    }

    public function update(StokOpname $stokOpname, array $data): StokOpname
    {
        $this->assertDraft($stokOpname);

        return DB::transaction(function () use ($stokOpname, $data) {
            $stokOpname->stokOpnameItems()->delete();

            $stokOpname->update([
                'opname_date' => $data['opname_date'],
                'notes' => $data['notes'] ?? null,
            ]);

            $this->createItems($stokOpname, $data['items']);

            return $stokOpname;
        });
    }

    public function destroy(StokOpname $stokOpname): void
    {
        $this->assertDraft($stokOpname);

        DB::transaction(function () use ($stokOpname) {
            $stokOpname->stokOpnameItems()->delete();
            $stokOpname->delete();
        });
    }

    public function submit(StokOpname $stokOpname): StokOpname
    {
        $this->assertDraft($stokOpname);

        $stokOpname->update(['status' => StokOpnameStatus::IN_PROGRESS]);

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER],
            title: 'Stok Opname Disubmit',
            body: 'Stok opname berhasil disubmit oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.stok-opnames.index', ['highlight' => $stokOpname->id]),
        );

        return $stokOpname;
    }

    public function verify(StokOpname $stokOpname, array $data): StokOpname
    {
        $this->assertInProgress($stokOpname);

        return DB::transaction(function () use ($stokOpname, $data) {
            $stokOpname->load('stokOpnameItems');

            foreach ($stokOpname->stokOpnameItems as $item) {
                if ($item->difference != 0) {
                    $this->adjustVariantStock(
                        $item->product_variant_id,
                        abs($item->difference),
                        $item->difference > 0 ? 1 : -1,
                        $item->stock_quality->value,
                    );
                }
            }

            $stokOpname->update([
                'status' => StokOpnameStatus::VERIFIED,
                'verified_by_id' => auth()->id(),
                'verification_notes' => $data['verification_notes'] ?? null,
            ]);

            NotificationService::notify(
                roles: [Role::STOK_OPNAME, Role::ADMIN_TOKO],
                title: 'Stok Opname Diverifikasi',
                body: 'Stok opname berhasil diverifikasi oleh '.auth()->user()->full_name.'.',
                url: route('admin.manage.stok-opnames.index', ['highlight' => $stokOpname->id]),
            );

            return $stokOpname;
        });
    }

    public function reject(StokOpname $stokOpname): StokOpname
    {
        $this->assertInProgress($stokOpname);

        $stokOpname->update([
            'status' => StokOpnameStatus::DRAFT,
            'verification_notes' => null,
        ]);

        NotificationService::notify(
            roles: [Role::STOK_OPNAME],
            title: 'Stok Opname Ditolak',
            body: 'Stok opname ditolak oleh '.auth()->user()->full_name.'. Silakan periksa dan submit ulang.',
            url: route('admin.manage.stok-opnames.index', ['highlight' => $stokOpname->id]),
        );

        return $stokOpname;
    }

    public function cancel(StokOpname $stokOpname): StokOpname
    {
        if ($stokOpname->status === StokOpnameStatus::VERIFIED) {
            throw ValidationException::withMessages([
                'status' => 'Stok opname yang sudah diverifikasi tidak dapat dibatalkan.',
            ]);
        }

        $stokOpname->update(['status' => StokOpnameStatus::CANCELLED]);

        return $stokOpname;
    }

    public function getForEdit(StokOpname $stokOpname): array
    {
        $stokOpname->load([
            'stokOpnameItems' => fn ($q) => $q
                ->select(['id', 'stok_opname_id', 'product_variant_id', 'stock_quality', 'system_stock', 'physical_stock', 'difference', 'notes']),
            'stokOpnameItems.productVariant:id,product_id,name,stock,reject_stock,retail_stock',
            'stokOpnameItems.productVariant.product:id,name',
        ]);

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

    public function getItems(StokOpname $stokOpname): \Illuminate\Support\Collection
    {
        return $stokOpname->stokOpnameItems()
            ->select(['id', 'stok_opname_id', 'product_variant_id', 'stock_quality', 'system_stock', 'physical_stock', 'difference', 'notes'])
            ->with(['productVariant:id,product_id,name', 'productVariant.product:id,name'])
            ->orderByRaw('(SELECT name FROM product_variants WHERE product_variants.id = stok_opname_items.product_variant_id)')
            ->get()
            ->map(function (StokOpnameItem $item) {
                $media = $item->productVariant?->getFirstMedia('images');
                $photoUrl = $media ? $this->s3Service->getTemporaryUrl($media->getPath()) : null;
                $photoConversionUrl = $media
                    ? ($media->getGeneratedConversions()->contains('thumb')
                        ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                        : $this->s3Service->getTemporaryUrl($media->getPath()))
                    : null;

                return [
                    'id' => $item->id,
                    'product_variant_id' => $item->product_variant_id,
                    'stock_quality' => $item->stock_quality,
                    'system_stock' => $item->system_stock,
                    'physical_stock' => $item->physical_stock,
                    'difference' => $item->difference,
                    'notes' => $item->notes,
                    'variant_name' => $item->productVariant?->name,
                    'product_name' => $item->productVariant?->product?->name,
                    'photo_url' => $photoUrl,
                    'photo_conversion_url' => $photoConversionUrl,
                ];
            });
    }

    private function createItems(StokOpname $stokOpname, array $items): void
    {
        $now = now();

        $rows = collect($items)->map(function ($item) use ($stokOpname, $now) {
            $variant = ProductVariant::findOrFail($item['product_variant_id']);
            $stockQuality = ProductStockQuality::from($item['stock_quality']);
            $systemStock = match ($stockQuality) {
                ProductStockQuality::GOOD => $variant->stock,
                ProductStockQuality::REJECT => $variant->reject_stock,
                ProductStockQuality::RETAIL => $variant->retail_stock,
            };

            return [
                'stok_opname_id' => $stokOpname->id,
                'product_variant_id' => $item['product_variant_id'],
                'stock_quality' => $stockQuality->value,
                'system_stock' => $systemStock,
                'physical_stock' => (int) $item['physical_stock'],
                'difference' => (int) $item['physical_stock'] - $systemStock,
                'notes' => $item['notes'] ?? null,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        })->toArray();

        DB::table('stok_opname_items')->insert($rows);
    }

    private function assertDraft(StokOpname $stokOpname): void
    {
        if ($stokOpname->status !== StokOpnameStatus::DRAFT) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname dengan status draft yang dapat diubah.',
            ]);
        }
    }

    private function assertInProgress(StokOpname $stokOpname): void
    {
        if ($stokOpname->status !== StokOpnameStatus::IN_PROGRESS) {
            throw ValidationException::withMessages([
                'status' => 'Hanya stok opname dengan status dalam proses yang dapat diproses.',
            ]);
        }
    }
}
