<?php

namespace App\Services\Purchase;

use App\Models\Item;
use App\Models\Purchase;
use App\Models\Supplier;
use App\Models\SupplierLedgerEntry;
use Illuminate\Support\Facades\DB;
use App\Scopes\StoreScope;
use App\Services\Accounting\AccountingVoucherService;
use App\Services\Inventory\StockMovementService;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;

class PurchaseService
{
    public function __construct(
        private readonly StockMovementService $stockMovementService,
        private readonly AccountingVoucherService $accountingVoucherService,
    ) {
    }

    public function create(array $data, ?int $adminId = null): Purchase
    {
        return DB::transaction(function () use ($data, $adminId) {
            $lines = $this->normalizePurchaseLines($data);
            if (count($lines) === 0) {
                throw ValidationException::withMessages([
                    'items' => translate('Please add at least one product to continue.'),
                ]);
            }

            $purchaseItems = [];
            $storeIds = [];
            $moduleId = null;
            $totalQuantity = 0;
            $totalAmount = 0;
            $resolvedLines = [];

            foreach ($lines as $index => $line) {
                $item = Item::withoutGlobalScopes()->withoutGlobalScope(StoreScope::class)->findOrFail($line['item_id']);

                if ($moduleId === null) {
                    $moduleId = $item->module_id;
                } elseif ((int) $moduleId !== (int) $item->module_id) {
                    throw ValidationException::withMessages([
                        "items.$index.item_id" => translate('Please select products from the same module in one purchase entry.'),
                    ]);
                }

                $quantity = (int) $line['quantity'];
                $purchasePrice = (float) $line['purchase_price'];
                $lineTotalAmount = round($quantity * $purchasePrice, 8);
                $variationType = $line['variation_type'] ?? null;

                $this->assertVariationSelection(
                    item: $item,
                    variationType: $variationType,
                    errorKey: "items.$index.variation_type"
                );

                $storeIds[] = $item->store_id;
                $totalQuantity += $quantity;
                $totalAmount = round($totalAmount + $lineTotalAmount, 8);
                $resolvedLines[] = compact('item', 'variationType', 'quantity', 'purchasePrice', 'lineTotalAmount');
            }

            $storeIds = collect($storeIds)
                ->filter(fn ($storeId) => filled($storeId))
                ->unique()
                ->values();

            $supplier = $this->resolveSupplier($data);
            $paidAmount = round((float) ($data['paid_amount'] ?? 0), 8);
            $paidAmount = min(max($paidAmount, 0), $totalAmount);
            $dueAmount = round($totalAmount - $paidAmount, 8);

            $purchase = Purchase::create([
                'module_id' => $moduleId,
                'store_id' => $storeIds->count() === 1 ? $storeIds->first() : null,
                'purchase_date' => $data['purchase_date'],
                'supplier_id' => $supplier?->id,
                'supplier_name' => $data['supplier_name'] ?? null,
                'invoice_number' => $data['invoice_number'] ?? null,
                'total_quantity' => $totalQuantity,
                'total_amount' => $totalAmount,
                'payment_status' => $this->paymentStatus($totalAmount, $paidAmount),
                'paid_amount' => $paidAmount,
                'due_amount' => $dueAmount,
                'note' => $data['note'] ?? null,
                'created_by_admin_id' => $adminId,
            ]);

            $purchase->update([
                'purchase_no' => 'PUR-' . str_pad((string) $purchase->id, 6, '0', STR_PAD_LEFT),
            ]);

            foreach ($resolvedLines as $resolvedLine) {
                $movement = $this->stockMovementService->increase($resolvedLine['item']->id, $resolvedLine['quantity'], [
                    'variation_type' => $resolvedLine['variationType'],
                    'source_type' => 'purchase',
                    'source_id' => $purchase->id,
                    'source_reference' => $purchase->purchase_no,
                    'sale_channel' => 'admin/manual',
                    'created_by_admin_id' => $adminId,
                ]);

                $purchase->items()->create([
                    'item_id' => $resolvedLine['item']->id,
                    'item_name' => $resolvedLine['item']->name,
                    'variation_type' => $resolvedLine['variationType'],
                    'quantity' => $resolvedLine['quantity'],
                    'purchase_price' => $resolvedLine['purchasePrice'],
                    'total_amount' => $resolvedLine['lineTotalAmount'],
                    'stock_before' => $movement->stock_before,
                    'stock_after' => $movement->stock_after,
                    'variation_stock_before' => $movement->variation_stock_before,
                    'variation_stock_after' => $movement->variation_stock_after,
                    'stock_movement_id' => $movement->id,
                ]);
            }

            $voucher = $this->accountingVoucherService->postPurchase($purchase, $paidAmount, $adminId);
            $this->setIfColumnExists($purchase, 'voucher_id', $voucher->id);
            $purchase->save();

            $this->recordSupplierLedger($purchase, $supplier, $dueAmount, $voucher->id, $adminId);

            return $purchase->fresh(['items.item', 'store', 'module', 'createdByAdmin']);
        });
    }

    private function normalizePurchaseLines(array $data): array
    {
        $lines = $data['items'] ?? [];
        if (! is_array($lines) || count($lines) === 0) {
            if (! empty($data['item_id'])) {
                $lines = [[
                    'item_id' => $data['item_id'],
                    'variation_type' => $data['variation_type'] ?? null,
                    'quantity' => $data['quantity'] ?? null,
                    'purchase_price' => $data['purchase_price'] ?? null,
                ]];
            } else {
                return [];
            }
        }

        return collect($lines)
            ->map(function ($line) {
                return [
                    'item_id' => data_get($line, 'item_id'),
                    'variation_type' => blank(data_get($line, 'variation_type')) ? null : data_get($line, 'variation_type'),
                    'quantity' => data_get($line, 'quantity'),
                    'purchase_price' => data_get($line, 'purchase_price'),
                ];
            })
            ->values()
            ->all();
    }

    private function assertVariationSelection(Item $item, ?string $variationType, string $errorKey = 'variation_type'): void
    {
        $rawVariations = $item->variations;
        if (blank($rawVariations)) {
            return;
        }

        $variations = is_array($rawVariations) ? $rawVariations : json_decode($rawVariations, true);
        if (! is_array($variations) || count($variations) === 0) {
            return;
        }

        if (blank($variationType)) {
            throw ValidationException::withMessages([
                $errorKey => translate('Please select a product variation for this item.'),
            ]);
        }

        $matched = collect($variations)->contains(fn ($variation) => ($variation['type'] ?? null) === $variationType);
        if (! $matched) {
            throw ValidationException::withMessages([
                $errorKey => translate('Selected variation was not found for the chosen product.'),
            ]);
        }
    }

    private function resolveSupplier(array $data): ?Supplier
    {
        if (! Schema::hasTable('suppliers') || blank($data['supplier_name'] ?? null)) {
            return null;
        }

        return Supplier::firstOrCreate(
            ['name' => $data['supplier_name']],
            ['phone' => $data['supplier_phone'] ?? null, 'status' => 'active']
        );
    }

    private function paymentStatus(float $totalAmount, float $paidAmount): string
    {
        if ($paidAmount <= 0) {
            return 'unpaid';
        }

        return $paidAmount >= $totalAmount ? 'paid' : 'partial';
    }

    private function setIfColumnExists(Purchase $purchase, string $column, mixed $value): void
    {
        if (Schema::hasColumn($purchase->getTable(), $column)) {
            $purchase->{$column} = $value;
        }
    }

    private function recordSupplierLedger(Purchase $purchase, ?Supplier $supplier, float $dueAmount, ?int $voucherId, ?int $adminId): void
    {
        if (! $supplier || $dueAmount <= 0 || ! Schema::hasTable('supplier_ledger_entries')) {
            return;
        }

        $lastBalance = (float) SupplierLedgerEntry::where('supplier_id', $supplier->id)->latest('id')->value('balance');
        SupplierLedgerEntry::create([
            'supplier_id' => $supplier->id,
            'source_type' => 'purchase',
            'source_id' => $purchase->id,
            'source_reference' => $purchase->purchase_no,
            'debit' => 0,
            'credit' => $dueAmount,
            'balance' => round($lastBalance + $dueAmount, 8),
            'voucher_id' => $voucherId,
            'created_by_admin_id' => $adminId,
        ]);
    }
}
