<?php

namespace Modules\Inventory\Services;

use Illuminate\Support\Collection;
use Modules\Billing\Enums\BillPayStatusEnum;
use Modules\Core\Enums\AmountTypeEnum;
use Modules\Core\Enums\PriceTypeEnum;
use Modules\Core\Enums\VatTypeEnum;
use Modules\Core\Exceptions\ApiValidationException;
use Modules\Inventory\Models\Product;
use Modules\Inventory\Models\ProductUnitConversion;

class PurchaseCalculationService
{
    public function __construct(
        protected VatCalculationService $vatService
    ){ }

    private function validateDuplicateProducts(array $items): void
    {
        $duplicates = collect($items)->pluck('product_id')->duplicates();

        if ($duplicates->isNotEmpty()) {
            ApiValidationException::message('Duplicate products are not allowed in the same purchase invoice.');
        }
    }

    private function validateProductRequirements(Product $product, array $item, int $index): void
    {
        $errors = [];

        if ($product->has_batch && blank($item['batch_no'] ?? null)) {
            $errors["items.{$index}.batch_no"] = [
                "{$product->name} requires batch number."
            ];
        }

        if ($product->has_expiry && blank($item['expiry_date'] ?? null)) {
            $errors["items.{$index}.expiry_date"] = [
                "{$product->name} requires expiry date."
            ];
        }

        if ($errors) {
            ApiValidationException::errors($errors);
        }
    }

    public function calculate(array $items): array
    {
        $this->validateDuplicateProducts($items);

        $calculatedItems = [];

        $subtotal = 0;
        $totalDiscount = 0;
        $taxableAmount = 0;
        $totalVat = 0;
        $grandTotal = 0;

        $products = $this->loadProducts($items);

        foreach ($items as $index => $item) {
            $product = $products[$item['product_id']];

            $this->validateProductRequirements(
                product: $product,
                item: $item,
                index: $index
            );

            $calculated = $this->calculateItem(
                product: $product,
                item: $item
            );

            $calculatedItems[] = $calculated;

            $subtotal += $calculated['line_total'];
            $totalDiscount += $calculated['discount_amount'];
            $taxableAmount += $calculated['taxable_amount'];
            $totalVat += $calculated['vat_amount'];
            $grandTotal += $calculated['total'];
        }

        return [
            'items' => $calculatedItems,
            'subtotal' => round($subtotal, 2),
            'total_discount' => round($totalDiscount, 2),
            'taxable_amount' => round($taxableAmount, 2),
            'total_vat' => round($totalVat, 2),
            'rounding_adjustment' => 0,
            'grand_total' => round($grandTotal, 2),
            'paid_amount' => 0,
            'due_amount' => round($grandTotal, 2),
            'payment_status' => BillPayStatusEnum::UNPAID->value,
        ];
    }

    private function loadProducts(array $items): Collection
    {
        $productIds = collect($items)->pluck('product_id')->unique();
        $unitIds = collect($items)->pluck('unit_id')->unique();

        return Product::query()
            ->with(['unitConversions' => fn ($query) =>
                $query->whereIn('from_unit_id', $unitIds)
            ])
            ->whereIn('id', $productIds)
            ->get()
            ->keyBy('id');
    }

    private function calculateItem(Product $product, array $item): array
    {
        $qty = (float) $item['qty'];
        $unitPrice = (float) $item['unit_price'];

        $discountType = $item['discount_type'] ?? null;
        $discountInput = (float) ($item['discount_amount'] ?? 0);
        $vatRate = (float) $product->vat_rate;
        $vatType = $product->vat_type;
        $vatPriceType = $item['vat_price_type'] ?? $product->vat_price_type;

        $baseQty = $this->calculateBaseQty(
            product: $product,
            unitId: $item['unit_id'],
            qty: $qty
        );

        $lineTotal = $this->calculateLineTotal(
            qty: $qty,
            unitPrice: $unitPrice
        );

        $discountAmount = $this->calculateDiscount(
            lineTotal: $lineTotal,
            discountType: $discountType,
            discountInput: $discountInput
        );

        $taxableAmount = $this->calculateTaxableAmount(
            lineTotal: $lineTotal,
            discountAmount: $discountAmount
        );

        [$vatAmount, $netAmount] = $this->calculateVat(
            taxableAmount: $taxableAmount,
            vatRate: $vatRate,
            vatType: $vatType,
            vatPriceType: $vatPriceType
        );

        $costPrice = $this->calculateCostPrice(
            netAmount: $netAmount,
            baseQty: $baseQty
        );

        $total = $this->calculateFinalTotal(
            taxableAmount: $taxableAmount,
            vatAmount: $vatAmount,
            vatPriceType: $vatPriceType
        );

        return [
            'purchase_requisition_item_id' => $item['purchase_requisition_item_id'] ?? null,
            'product_id' => $product->id,
            'unit_id' => $item['unit_id'],
            'qty' => $qty,
            'base_qty' => $baseQty,
            'unit_price' => $unitPrice,
            'vat_price_type' => $vatPriceType,
            'line_total' => $lineTotal,
            'discount_type' => $discountType,
            'discount_amount' => $discountAmount,
            'taxable_amount' => $taxableAmount,
            'vat_type' => $vatType,
            'vat_rate' => $vatRate,
            'vat_amount' => $vatAmount,
            'net_amount' => $netAmount,
            'cost_price' => $costPrice,
            'total' => $total,
            'batch_no' => $item['batch_no'] ?? null,
            'expiry_date' => $item['expiry_date'] ?? null,
        ];
    }

    private function calculateLineTotal(float $qty, float $unitPrice): float
    {
        return round($qty * $unitPrice, 2);
    }

    private function calculateDiscount(float $lineTotal, ?string $discountType, float $discountInput): float
    {
        $discount = match ($discountType) {
            AmountTypeEnum::PERCENTAGE->value => round($lineTotal * ($discountInput / 100), 2),
            AmountTypeEnum::FIXED->value => round($discountInput, 2),
            default => 0,
        };

        return min($discount, $lineTotal);
    }

    private function calculateTaxableAmount(float $lineTotal, float $discountAmount): float
    {
        return round($lineTotal - $discountAmount, 2);
    }

    private function calculateVat(float $taxableAmount, float $vatRate, string $vatType, string $vatPriceType): array
    {
        if (!$this->vatService->isVatApplicable($vatType, $vatRate)) {
            return [0, $taxableAmount];
        }

        $vatAmount = 0;
        $netAmount = $taxableAmount;

        if ($vatPriceType === PriceTypeEnum::EXCLUSIVE->value) {
            $vatAmount = round($taxableAmount * ($vatRate / 100), 2);
            $netAmount = $taxableAmount;
        } else {
            $vatAmount = round($taxableAmount - ($taxableAmount / (1 + ($vatRate / 100))), 2);
            $netAmount = round($taxableAmount - $vatAmount, 2);
        }

        return [$vatAmount, $netAmount];
    }

    private function isVatApplicable(string $vatType, float $vatRate): bool
    {
        return $vatType === VatTypeEnum::STANDARD->value && $vatRate > 0;
    }

    private function calculateCostPrice(float $netAmount, float $baseQty): float
    {
        return $baseQty > 0 ? round($netAmount / $baseQty, 6) : 0;
    }

    private function calculateFinalTotal(float $taxableAmount, float $vatAmount, string $vatPriceType): float
    {
        return $vatPriceType === PriceTypeEnum::EXCLUSIVE->value ? round(
            $taxableAmount + $vatAmount, 2
        ) : round(
            $taxableAmount, 2
        );
    }

    private function calculateBaseQty(Product $product, int $unitId, float $qty): float
    {
        if ($unitId == $product->base_unit_id) {
            return round($qty, 6);
        }

        $conversion = $this->getConversion(
            product: $product,
            unitId: $unitId
        );

        if (!$conversion) {
            ApiValidationException::errors([
                'unit_id' => [
                    'Invalid unit conversion.'
                ]
            ]);
        }

        return round(($qty * $conversion->to_qty) / $conversion->from_qty, 6);
    }

    private function getConversion(Product $product, int $unitId): ?ProductUnitConversion
    {
        return $product->unitConversions->firstWhere('from_unit_id', $unitId);
    }
}
