<?php

namespace App\Services\Admin\Manage;

use App\Enums\Role;
use App\Models\Purchase;
use App\Models\PurchaseItem;
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Models\Supplier;
use App\Services\Concerns\RegistersMedia;
use App\Services\NotificationService;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class PurchaseService
{
    use RegistersMedia;

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

    public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = [], ?int $highlight = null): LengthAwarePaginator
    {
        $itemCountQuery = '(SELECT COUNT(*) FROM purchase_items WHERE purchase_items.purchase_id = purchases.id AND purchase_items.deleted_at IS NULL)';
        $totalQtyQuery = '(SELECT IFNULL(SUM(quantity), 0) FROM purchase_items WHERE purchase_items.purchase_id = purchases.id AND purchase_items.deleted_at IS NULL)';
        $materialNameQuery = '(SELECT raw_materials.name FROM purchase_items JOIN raw_material_prices ON raw_material_prices.id = purchase_items.raw_material_price_id JOIN raw_materials ON raw_materials.id = raw_material_prices.raw_material_id WHERE purchase_items.purchase_id = purchases.id AND purchase_items.deleted_at IS NULL LIMIT 1)';
        $unitQuery = '(SELECT raw_materials.unit FROM purchase_items JOIN raw_material_prices ON raw_material_prices.id = purchase_items.raw_material_price_id JOIN raw_materials ON raw_materials.id = raw_material_prices.raw_material_id WHERE purchase_items.purchase_id = purchases.id AND purchase_items.deleted_at IS NULL LIMIT 1)';

        $paginator = Purchase::query()
            ->select(['id', 'supplier_id', 'created_by_id', 'subtotal', 'discount', 'shipping_cost', 'total', 'notes', 'created_at'])
            ->with([
                'supplier:id,name',
                'createdBy:id',
                'createdBy.userProfile:id,user_id,full_name',
            ])
            ->selectRaw("{$itemCountQuery} as variants_count")
            ->selectRaw("{$totalQtyQuery} as total_qty")
            ->selectRaw("{$materialNameQuery} as material_name")
            ->selectRaw("{$unitQuery} as unit")
            ->when($highlight, fn ($q) => $q->where('id', $highlight))
            ->when($search, function ($q) use ($search) {
                $q->whereHas('supplier', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
                    ->orWhere('notes', 'like', "%{$search}%");
            })
            ->when($filters['supplier_id'] ?? null, fn ($q, $supplierId) => $q->where('supplier_id', $supplierId))
            ->orderBy($sort, $direction)
            ->paginate($perPage);

        $paginator->getCollection()->each(function (Purchase $purchase) {
            $purchaseMedia = $purchase->getMedia('photos');
            $purchase->photo_urls = $purchaseMedia->map(
                fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
            )->toArray();
            $purchase->photo_conversion_urls = $purchaseMedia->map(
                fn ($media) => $media->getGeneratedConversions()->contains('thumb')
                    ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                    : $this->s3Service->getTemporaryUrl($media->getPath())
            )->toArray();
        });

        return $paginator;
    }

    public function getItems(Purchase $purchase): Collection
    {
        return $purchase->purchaseItems()
            ->select(['id', 'purchase_id', 'raw_material_price_id', 'quantity', 'unit_price', 'subtotal'])
            ->with(['rawMaterialPrice:id,raw_material_id,variant,price,stock', 'rawMaterialPrice.rawMaterial:id,name,unit'])
            ->orderByRaw('(SELECT variant FROM raw_material_prices WHERE raw_material_prices.id = purchase_items.raw_material_price_id)')
            ->get()
            ->each(function (PurchaseItem $item) {
                if (! $item->rawMaterialPrice) {
                    return;
                }

                $media = $item->rawMaterialPrice->getFirstMedia('images');
                $item->rawMaterialPrice->photo_url = $media
                    ? $this->s3Service->getTemporaryUrl($media->getPath())
                    : null;
                $item->rawMaterialPrice->photo_conversion_url = $media
                    ? ($media->getGeneratedConversions()->contains('thumb')
                        ? $this->s3Service->getTemporaryUrl($media->getPath('thumb'))
                        : $this->s3Service->getTemporaryUrl($media->getPath()))
                    : null;
            });
    }

    public function getForCreate(): array
    {
        return [
            'suppliers' => Supplier::select(['id', 'name'])->latest()->get(),
            'rawMaterials' => RawMaterial::query()
                ->select(['id', 'name', 'unit', 'is_active'])
                ->with([
                    'rawMaterialPrices:id,raw_material_id,variant,price,stock',
                ])
                ->orderBy('name')
                ->get(),
        ];
    }

    public function getForEdit(Purchase $purchase): array
    {
        $purchase->load([
            'purchaseItems' => fn ($q) => $q
                ->orderByRaw('(SELECT variant FROM raw_material_prices WHERE raw_material_prices.id = purchase_items.raw_material_price_id)'),
            'purchaseItems.rawMaterialPrice.rawMaterial',
            'supplier',
        ]);

        $rawMaterial = $purchase->purchaseItems->first()?->rawMaterialPrice?->rawMaterial;

        $variants = $purchase->purchaseItems->map(function (PurchaseItem $item) {
            if (! $item->rawMaterialPrice) {
                return null;
            }

            $media = $item->rawMaterialPrice->getFirstMedia('images');

            return [
                'id' => $item->rawMaterialPrice->id,
                'variant' => $item->rawMaterialPrice->variant,
                'price' => $item->unit_price,
                'stock' => $item->quantity,
                'photo_key' => $media?->getCustomProperty('s3_key') ?? $media?->file_name,
                'photo_url' => $media ? $this->s3Service->getTemporaryUrl($media->getPath()) : null,
            ];
        })->filter()->values();

        $items = $purchase->purchaseItems;
        $items = $items->filter(fn (PurchaseItem $item) => $item->rawMaterialPrice !== null);

        $materials = $items
            ->map(fn (PurchaseItem $item) => $item->rawMaterialPrice->rawMaterial)
            ->filter()
            ->unique(fn (RawMaterial $material) => $material->id);

        $singleMaterial = $materials->count() === 1;
        $sharedWithOther = PurchaseItem::where('purchase_id', '!=', $purchase->id)
            ->whereIn('raw_material_price_id', $items->pluck('raw_material_price_id'))
            ->exists();

        $purchaseMedia = $purchase->getMedia('photos');
        $purchasePhotoKeys = $purchaseMedia->map(
            fn ($media) => $media->getCustomProperty('s3_key') ?? $media->file_name
        )->toArray();
        $purchasePhotoUrls = $purchaseMedia->map(
            fn ($media) => $this->s3Service->getTemporaryUrl($media->getPath())
        )->toArray();

        return [
            'id' => $purchase->id,
            'name' => $rawMaterial?->name ?? '',
            'unit' => $rawMaterial?->unit->value ?? 'kg',
            'supplier_id' => $purchase->supplier_id,
            'discount' => $purchase->discount,
            'shipping_cost' => $purchase->shipping_cost,
            'notes' => $purchase->notes,
            'photo_keys' => $purchasePhotoKeys,
            'photo_urls' => $purchasePhotoUrls,
            'variants' => $variants,
            'default_mode' => $singleMaterial && ! $sharedWithOther ? 'new' : 'existing',
            'existing_material_name' => $singleMaterial ? $materials->first()->name : null,
            'existing_quantities' => $singleMaterial
                ? $items->mapWithKeys(fn (PurchaseItem $item) => [
                    (int) $item->raw_material_price_id => (int) $item->quantity,
                ])->all()
                : [],
        ];
    }

    public function store(array $data): Purchase
    {
        if (($data['mode'] ?? 'new') === 'existing') {
            return $this->storeFromExisting($data);
        }

        return $this->storeNew($data);
    }

    private function storeFromExisting(array $data): Purchase
    {
        return DB::transaction(function () use ($data) {
            $now = now();
            $subtotal = 0;

            $itemRows = collect($data['existing_items'])->map(function ($item) use ($now, &$subtotal) {
                $itemSubtotal = (int) ($item['unit_price'] * $item['quantity']);
                $subtotal += $itemSubtotal;

                return [
                    'purchase_id' => null,
                    'raw_material_price_id' => $item['raw_material_price_id'],
                    'user_id' => auth()->id(),
                    'quantity' => $item['quantity'],
                    'unit_price' => $item['unit_price'],
                    'subtotal' => $itemSubtotal,
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            })->toArray();

            $discount = $data['discount'] ?? 0;
            $shippingCost = $data['shipping_cost'] ?? 0;
            $total = $subtotal - $discount + $shippingCost;

            $purchase = Purchase::create([
                'supplier_id' => $data['supplier_id'],
                'created_by_id' => auth()->id(),
                'subtotal' => $subtotal,
                'discount' => $discount,
                'shipping_cost' => $shippingCost,
                'total' => $total,
                'notes' => $data['notes'] ?? null,
            ]);

            foreach ($itemRows as &$row) {
                $row['purchase_id'] = $purchase->id;
            }
            DB::table('purchase_items')->insert($itemRows);

            $stockIncrementMap = collect($data['existing_items'])
                ->groupBy('raw_material_price_id')
                ->map(fn ($group) => $group->sum('quantity'));

            foreach ($stockIncrementMap as $priceId => $totalQty) {
                RawMaterialPrice::where('id', $priceId)->increment('stock', $totalQty);
            }

            if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) {
                $this->registerPhotos(
                    model: $purchase,
                    photoKeys: $data['photo_keys'],
                    collectionName: 'photos',
                );
            }

            NotificationService::notify(
                roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
                title: 'Belanja Baru',
                body: 'Belanja bahan baku sebesar Rp '.number_format($total, 0, ',', '.').' berhasil dicatat oleh '.auth()->user()->full_name.'.',
                url: route('admin.manage.purchases.index', ['highlight' => $purchase->id]),
            );

            return $purchase;
        });
    }

    private function storeNew(array $data): Purchase
    {
        return DB::transaction(function () use ($data) {
            $rawMaterial = RawMaterial::create([
                'name' => $data['name'],
                'unit' => $data['unit'],
                'is_active' => true,
            ]);

            $subtotal = 0;
            $now = now();
            $priceRows = collect($data['variants'])->map(function ($v) use ($rawMaterial, $now, &$subtotal) {
                $itemSubtotal = (int) ($v['price'] * $v['stock']);
                $subtotal += $itemSubtotal;

                return [
                    'raw_material_id' => $rawMaterial->id,
                    'variant' => $v['variant'],
                    'price' => $v['price'],
                    'stock' => $v['stock'],
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            })->toArray();

            DB::table('raw_material_prices')->insert($priceRows);

            $priceIdMap = DB::table('raw_material_prices')
                ->where('raw_material_id', $rawMaterial->id)
                ->pluck('id', 'variant')
                ->toArray();

            foreach ($data['variants'] as $variantData) {
                if (! empty($variantData['photo_key'])) {
                    $priceId = $priceIdMap[$variantData['variant']] ?? null;
                    if ($priceId) {
                        $priceModel = RawMaterialPrice::find($priceId);
                        $this->registerMedia(
                            model: $priceModel,
                            s3Key: $variantData['photo_key'],
                            collectionName: 'images',
                            orderColumn: 1,
                        );
                    }
                }
            }

            $discount = $data['discount'] ?? 0;
            $shippingCost = $data['shipping_cost'] ?? 0;
            $total = $subtotal - $discount + $shippingCost;

            $purchase = Purchase::create([
                'supplier_id' => $data['supplier_id'],
                'created_by_id' => auth()->id(),
                'subtotal' => $subtotal,
                'discount' => $discount,
                'shipping_cost' => $shippingCost,
                'total' => $total,
                'notes' => $data['notes'] ?? null,
            ]);

            $purchaseItems = collect($data['variants'])->map(function ($v) use ($purchase, $priceIdMap, $now) {
                $priceId = $priceIdMap[$v['variant']] ?? null;

                return [
                    'purchase_id' => $purchase->id,
                    'raw_material_price_id' => $priceId,
                    'user_id' => auth()->id(),
                    'quantity' => $v['stock'],
                    'unit_price' => $v['price'],
                    'subtotal' => (int) ($v['price'] * $v['stock']),
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            })->toArray();

            DB::table('purchase_items')->insert($purchaseItems);

            if (! empty($data['photo_keys']) && is_array($data['photo_keys'])) {
                $this->registerPhotos(
                    model: $purchase,
                    photoKeys: $data['photo_keys'],
                    collectionName: 'photos',
                );
            }

            NotificationService::notify(
                roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
                title: 'Belanja Baru',
                body: 'Belanja bahan baku sebesar Rp '.number_format($total, 0, ',', '.').' berhasil dicatat oleh '.auth()->user()->full_name.'.',
                url: route('admin.manage.purchases.index', ['highlight' => $purchase->id]),
            );

            return $purchase;
        });
    }

    public function update(Purchase $purchase, array $data): Purchase
    {
        $purchase = DB::transaction(function () use ($purchase, $data) {
            $oldStockMap = $purchase->purchaseItems()
                ->select('raw_material_price_id', 'quantity')
                ->get()
                ->groupBy('raw_material_price_id')
                ->map(fn ($group) => $group->sum('quantity'));

            foreach ($oldStockMap as $priceId => $totalQty) {
                RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalQty);
            }

            $oldMaterial = $purchase->purchaseItems()
                ->join('raw_material_prices', 'raw_material_prices.id', '=', 'purchase_items.raw_material_price_id')
                ->join('raw_materials', 'raw_materials.id', '=', 'raw_material_prices.raw_material_id')
                ->select('raw_materials.*')
                ->first();

            $purchase->purchaseItems()->delete();

            $now = now();
            $subtotal = 0;

            if (($data['mode'] ?? 'new') === 'existing') {
                $itemRows = collect($data['existing_items'])->map(function ($item) use ($now, &$subtotal) {
                    $itemSubtotal = (int) ($item['unit_price'] * $item['quantity']);
                    $subtotal += $itemSubtotal;

                    return [
                        'purchase_id' => null,
                        'raw_material_price_id' => $item['raw_material_price_id'],
                        'user_id' => auth()->id(),
                        'quantity' => $item['quantity'],
                        'unit_price' => $item['unit_price'],
                        'subtotal' => $itemSubtotal,
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                })->toArray();

                $stockIncrementMap = collect($data['existing_items'])
                    ->groupBy('raw_material_price_id')
                    ->map(fn ($group) => $group->sum('quantity'));

                foreach ($stockIncrementMap as $priceId => $totalQty) {
                    RawMaterialPrice::where('id', $priceId)->increment('stock', $totalQty);
                }
            } else {
                if ($oldMaterial) {
                    $rawMaterial = $oldMaterial;
                    $rawMaterial->update([
                        'name' => $data['name'],
                        'is_active' => true,
                    ]);
                } else {
                    $rawMaterial = RawMaterial::create([
                        'name' => $data['name'],
                        'unit' => $data['unit'] ?? 'kg',
                        'is_active' => true,
                    ]);
                }

                $itemRows = collect($data['variants'])->map(function ($v) use ($purchase, $rawMaterial, $now, &$subtotal) {
                    $price = null;

                    if (! empty($v['id'])) {
                        $price = RawMaterialPrice::withTrashed()->find($v['id']);
                    }

                    if (! $price) {
                        $price = $rawMaterial->rawMaterialPrices()
                            ->where('variant', $v['variant'])
                            ->first();
                    }

                    if ($price) {
                        $price->increment('stock', (int) $v['stock']);
                        $price->update(['price' => $v['price']]);
                    } else {
                        $price = $rawMaterial->rawMaterialPrices()->create([
                            'variant' => $v['variant'],
                            'price' => $v['price'],
                            'stock' => $v['stock'],
                        ]);
                    }

                    if (! empty($v['photo_key']) && $price->getFirstMedia('images')?->getCustomProperty('s3_key') !== $v['photo_key']) {
                        $price->clearMediaCollection('images');
                        $this->registerMedia(
                            model: $price,
                            s3Key: $v['photo_key'],
                            collectionName: 'images',
                            orderColumn: 1,
                        );
                    }

                    $itemSubtotal = (int) ($v['price'] * $v['stock']);
                    $subtotal += $itemSubtotal;

                    return [
                        'purchase_id' => $purchase->id,
                        'raw_material_price_id' => $price->id,
                        'user_id' => auth()->id(),
                        'quantity' => $v['stock'],
                        'unit_price' => $v['price'],
                        'subtotal' => $itemSubtotal,
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                })->toArray();
            }

            $discount = $data['discount'] ?? 0;
            $shippingCost = $data['shipping_cost'] ?? 0;
            $total = $subtotal - $discount + $shippingCost;

            $purchase->update([
                'supplier_id' => $data['supplier_id'],
                'subtotal' => $subtotal,
                'discount' => $discount,
                'shipping_cost' => $shippingCost,
                'total' => $total,
                'notes' => $data['notes'] ?? null,
            ]);

            foreach ($itemRows as &$row) {
                $row['purchase_id'] = $purchase->id;
            }
            DB::table('purchase_items')->insert($itemRows);

            $this->syncPhotos($purchase, $data['photo_keys'] ?? [], 'photos');

            return $purchase;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Belanja Diperbarui',
            body: 'Belanja bahan baku sebesar '.$purchase->formatted_total.' berhasil diperbarui oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.purchases.index', ['highlight' => $purchase->id]),
        );

        return $purchase;
    }

    public function destroy(Purchase $purchase): bool
    {
        $result = DB::transaction(function () use ($purchase) {
            $stockDecrementMap = $purchase->purchaseItems()
                ->select('raw_material_price_id', 'quantity')
                ->get()
                ->groupBy('raw_material_price_id')
                ->map(fn ($group) => $group->sum('quantity'));

            foreach ($stockDecrementMap as $priceId => $totalQty) {
                RawMaterialPrice::where('id', $priceId)->decrement('stock', $totalQty);
            }

            $purchase->purchaseItems()->delete();
            $purchase->delete();

            return true;
        });

        NotificationService::notify(
            roles: [Role::OWNER, Role::DEVELOPER, Role::ADMIN_BAHAN_BAKU],
            title: 'Belanja Dihapus',
            body: 'Belanja bahan baku berhasil dihapus oleh '.auth()->user()->full_name.'.',
            url: route('admin.manage.purchases.index'),
        );

        return $result;
    }
}
