<?php

namespace App\Support;

class OrderMoney
{
    public const MINIMUM_METER_ITEM_TOTAL = 10000;
    public const ROUNDING_STEP = 1000;

    public static function normalizeUnit($unit): string
    {
        $unit = strtolower(trim((string) $unit));

        return match ($unit) {
            'm2', 'm²', 'meter2', 'meter persegi', 'per meter persegi' => 'm2',
            'meter', 'm', 'per meter' => 'meter',
            'pcs' => 'lembar',
            default => $unit !== '' ? $unit : 'lembar',
        };
    }

    public static function isMeterBased($unit): bool
    {
        return in_array(self::normalizeUnit($unit), ['m2', 'meter'], true);
    }

    public static function roundUp(float|int|null $amount, int $step = self::ROUNDING_STEP): float
    {
        $amount = (float) ($amount ?? 0);

        if ($amount <= 0) {
            return 0;
        }

        if ($step <= 1) {
            return (float) ceil($amount);
        }

        return (float) (ceil($amount / $step) * $step);
    }

    public static function meterMinimumAndRounding(float|int|null $amount): float
    {
        $amount = max((float) ($amount ?? 0), 0);

        if ($amount <= 0) {
            return 0;
        }

        if ($amount < self::MINIMUM_METER_ITEM_TOTAL) {
            $amount = self::MINIMUM_METER_ITEM_TOTAL;
        }

        return self::roundUp($amount, self::ROUNDING_STEP);
    }

    public static function itemSubtotal($item): float
    {
        if (!$item) {
            return 0;
        }

        $unit = self::normalizeUnit($item->size_unit ?? $item->unit ?? 'lembar');
        $isMeterBased = self::isMeterBased($unit);

        $manualPrice = (float) ($item->manual_price ?? 0);
        if ($manualPrice > 0) {
            return $isMeterBased ? self::roundUp($manualPrice, self::ROUNDING_STEP) : $manualPrice;
        }

        $rawSubtotal = (float) (
            $item->system_subtotal
            ?? $item->display_subtotal
            ?? $item->report_subtotal
            ?? $item->final_subtotal
            ?? $item->subtotal
            ?? 0
        );

        if ($rawSubtotal <= 0) {
            $qty = (float) ($item->quantity ?? $item->qty ?? 1);
            $price = (float) ($item->price ?? $item->unit_price ?? $item->base_price ?? 0);
            $sizeValue = (float) ($item->size_value ?? 1);

            if ($isMeterBased) {
                $rawSubtotal = $price * max($sizeValue, 0.0001) * max($qty, 1);
            } else {
                $rawSubtotal = $price * max($qty, 1);
            }
        }

        if ($rawSubtotal <= 0) {
            return 0;
        }

        return $isMeterBased ? self::meterMinimumAndRounding($rawSubtotal) : $rawSubtotal;
    }

    public static function items($order)
    {
        if (!$order) {
            return collect();
        }

        if (method_exists($order, 'relationLoaded') && $order->relationLoaded('items')) {
            return collect($order->items ?? []);
        }

        if (isset($order->items) && is_iterable($order->items)) {
            return collect($order->items);
        }

        if (method_exists($order, 'items')) {
            try {
                return collect($order->items()->get());
            } catch (\Throwable $e) {
                return collect();
            }
        }

        return collect();
    }

    public static function orderSubtotal($order): float
    {
        if (!$order) {
            return 0;
        }

        $items = self::items($order);

        if ($items->count() > 0) {
            return (float) $items->sum(fn ($item) => self::itemSubtotal($item));
        }

        $rawTotal = (float) (
            $order->display_total_price
            ?? $order->report_total_price
            ?? $order->total_before_ppn
            ?? $order->subtotal_before_ppn
            ?? $order->subtotal
            ?? $order->total_price
            ?? 0
        );

        $storedPpn = (float) ($order->ppn_amount ?? 0);

        if ((bool) ($order->use_ppn ?? false) && $storedPpn > 0 && $rawTotal >= $storedPpn) {
            $rawTotal -= $storedPpn;
        }

        return max($rawTotal, 0);
    }

    public static function ppn($order, ?float $subtotal = null): float
    {
        if (!$order || !(bool) ($order->use_ppn ?? false)) {
            return 0;
        }

        $storedPpn = (float) ($order->ppn_amount ?? 0);
        if ($storedPpn > 0) {
            return $storedPpn;
        }

        $rate = (float) ($order->ppn_rate ?? 0);
        if ($rate <= 0) {
            return 0;
        }

        $subtotal = $subtotal ?? self::orderSubtotal($order);

        return round($subtotal * ($rate / 100));
    }

    public static function orderGrandTotal($order): float
    {
        $subtotal = self::orderSubtotal($order);
        return $subtotal + self::ppn($order, $subtotal);
    }

    public static function paid($order): float
    {
        if (!$order) {
            return 0;
        }

        if (method_exists($order, 'relationLoaded') && $order->relationLoaded('payments')) {
            $payments = collect($order->payments ?? []);
            if ($payments->count() > 0) {
                return (float) $payments->sum('amount');
            }
        }

        if (isset($order->payments) && is_iterable($order->payments)) {
            $payments = collect($order->payments);
            if ($payments->count() > 0) {
                return (float) $payments->sum('amount');
            }
        }

        return (float) ($order->paid_amount ?? 0);
    }

    public static function remaining($order): float
    {
        return max(self::orderGrandTotal($order) - self::paid($order), 0);
    }

    public static function change($order): float
    {
        $change = max(self::paid($order) - self::orderGrandTotal($order), 0);

        return $change <= 1 ? 0 : $change;
    }

    public static function fmt(float|int|null $amount): string
    {
        return 'Rp ' . number_format((float) ($amount ?? 0), 0, ',', '.');
    }
}
