<?php

declare(strict_types=1);

namespace Maestro\Core\Modules\Offers\Services;

use Maestro\Core\Modules\Offers\Enums\OfferDiscountType;
use Maestro\Core\Modules\Offers\Enums\OfferPricingType;
use Maestro\Core\Modules\Offers\Enums\OfferSectionRole;
use Maestro\Core\Modules\Offers\Models\Offer;
use Maestro\Core\Modules\Offers\Models\OfferRow;

/**
 * Computes money breakdown and persists {@see Offer::$vat_total}.
 */
final class OfferTotalsService
{
    /**
     * @return array<string, mixed>
     */
    public static function summarize(Offer $offer): array
    {
        $offer->loadMissing(['rootSections.rows', 'rootSections.children.rows']);

        $sectionsOut = [];

        foreach ($offer->rootSections as $section) {
            $role = $section->section_role instanceof OfferSectionRole
                ? $section->section_role
                : OfferSectionRole::tryFrom((string) $section->section_role) ?? OfferSectionRole::Chapter;

            $secNet = 0.0;
            $secVat = 0.0;

            if ($role === OfferSectionRole::PricingRoot) {
                foreach ($section->children as $child) {
                    foreach ($child->rows as $row) {
                        [$net, $vat] = self::lineAmounts($row);
                        $secNet += $net;
                        $secVat += $vat;
                    }
                }
            } else {
                foreach ($section->rows as $row) {
                    [$net, $vat] = self::lineAmounts($row);
                    $secNet += $net;
                    $secVat += $vat;
                }
            }

            $sectionsOut[] = [
                'section_id' => $section->id,
                'title' => $section->title,
                'subtotal_excl_vat' => round($secNet, 4),
                'vat' => round($secVat, 4),
            ];
        }

        $linesNet = (float) array_sum(array_column($sectionsOut, 'subtotal_excl_vat'));
        $linesVat = (float) array_sum(array_column($sectionsOut, 'vat'));

        $offerDiscountAmount = self::offerDiscountAppliedAmount($offer, $linesNet);
        $totalExcl = max(0, round($linesNet - $offerDiscountAmount, 4));
        $factor = $linesNet > 0 ? $totalExcl / $linesNet : 0.0;
        $scaledVat = round($linesVat * $factor, 4);
        $grand = round($totalExcl + $scaledVat, 4);

        return [
            'sections' => $sectionsOut,
            'lines_net_subtotal' => round($linesNet, 4),
            'offer_discount_amount' => round(min($offerDiscountAmount, $linesNet), 4),
            'total_excl_vat' => $totalExcl,
            'vat_total' => $scaledVat,
            'grand_total' => $grand,
        ];
    }

    /**
     * @return array{0: float, 1: float}
     */
    public static function lineAmounts(OfferRow $row): array
    {
        $qty = (float) $row->quantity;
        $unit = (float) $row->unit_price;
        $net = round($qty * $unit, 4);

        $discountType = $row->discount_type instanceof OfferDiscountType
            ? $row->discount_type
            : OfferDiscountType::tryFrom((string) $row->discount_type) ?? OfferDiscountType::None;
        $discountValue = (float) $row->discount_value;

        if ($discountType === OfferDiscountType::Percent) {
            $net -= round($net * $discountValue / 100.0, 4);
        } elseif ($discountType === OfferDiscountType::Fixed) {
            $net -= $discountValue;
        }

        if ($net < 0) {
            $net = 0;
        }

        $rate = (float) $row->vat_rate;
        $vat = round($net * ($rate / 100.0), 4);

        return [$net, $vat];
    }

    /** Persists aggregated VAT on header for reporting. */
    public static function persistOfferVat(Offer $offer): void
    {
        $summary = self::summarize($offer);
        $offer->vat_total = (string) $summary['vat_total'];
        $offer->saveQuietly();
    }

    private static function offerDiscountAppliedAmount(Offer $offer, float $linesNetBeforeOfferDiscount): float
    {
        $rawType = $offer->discount_type;
        $type = $rawType instanceof OfferDiscountType
            ? $rawType
            : OfferDiscountType::tryFrom((string) $rawType) ?? OfferDiscountType::None;
        $value = (float) $offer->discount_value;

        return match ($type) {
            OfferDiscountType::None => 0.0,
            OfferDiscountType::Percent => round($linesNetBeforeOfferDiscount * ($value / 100.0), 4),
            OfferDiscountType::Fixed => round(min($value, $linesNetBeforeOfferDiscount), 4),
        };
    }

    public static function pricingLabel(OfferPricingType $pricingType): string
    {
        return match ($pricingType) {
            OfferPricingType::OneTime => 'one-time',
            OfferPricingType::Weekly => 'per week',
            OfferPricingType::Monthly => 'per month',
            OfferPricingType::Hourly => 'per hour',
            OfferPricingType::CustomUnit => '',
        };
    }
}
