<?php

namespace App\Services\Billing;

use App\Models\Setting;

/**
 * PRC-369 / M1 — Per-billing-cycle payment terms.
 *
 * Old model: one flat `default_payment_terms` (days) used by every
 * invoice + a separate `invoice_days_before_due` (days) for cron
 * lead-time. Both collapsed into a single per-cycle table:
 *
 *   payment_terms_per_cycle = {
 *     "monthly":       7,
 *     "quarterly":     14,
 *     "semi_annually": 21,
 *     "annually":      30,
 *     "biennially":    30,
 *     "domain":        45,   // TLD renewal lead-time
 *   }
 *
 * Semantics in the new model:
 *   - Lead-time = days BEFORE `service.next_due_date` to generate the
 *     invoice.
 *   - Payment window = the same value (invoice `due_date` is set to
 *     `next_due_date`, so the client has lead-time days to pay).
 *
 * Unknown cycles (legacy data, free/one-time) fall back to monthly.
 */
final class PaymentTermsResolver
{
    /** Canonical default table — used when the setting row is absent or
     *  the requested cycle is missing from the JSON payload. */
    public const DEFAULTS = [
        'monthly'       => 7,
        'quarterly'     => 14,
        'semi_annually' => 21,
        'annually'      => 30,
        'biennially'    => 30,
        'domain'        => 45,
    ];

    /**
     * Lead-time days for a given billing cycle. Returns the cycle's
     * value from `payment_terms_per_cycle`, or the canonical default,
     * or the monthly default for unknown cycles.
     */
    public static function daysForCycle(?string $cycle): int
    {
        $cycle = self::normaliseCycle($cycle);
        $table = self::table();

        return (int) ($table[$cycle] ?? self::DEFAULTS[$cycle] ?? self::DEFAULTS['monthly']);
    }

    /**
     * Full table (cycle → days). Always returns all 6 canonical cycles;
     * unset keys are filled from DEFAULTS so the settings UI always
     * renders every row.
     */
    public static function table(): array
    {
        $raw = Setting::getValue('payment_terms_per_cycle', null);
        $stored = [];

        if (is_string($raw) && $raw !== '') {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                $stored = $decoded;
            }
        } elseif (is_array($raw)) {
            $stored = $raw;
        }

        $out = [];
        foreach (self::DEFAULTS as $cycle => $default) {
            $value = $stored[$cycle] ?? $default;
            $out[$cycle] = max(0, (int) $value);
        }

        return $out;
    }

    /**
     * Normalise a free-form cycle string (handles legacy "Annually",
     * "One Time" etc.) to one of the canonical keys. Returns "monthly"
     * for anything unrecognised so a lead-time can always be computed.
     */
    public static function normaliseCycle(?string $cycle): string
    {
        if ($cycle === null || $cycle === '') {
            return 'monthly';
        }

        $key = strtolower(str_replace(['-', ' '], '_', $cycle));

        $map = [
            'monthly'         => 'monthly',
            'quarterly'       => 'quarterly',
            'semi_annually'   => 'semi_annually',
            'semiannually'    => 'semi_annually',
            'semi_annual'     => 'semi_annually',
            'annually'        => 'annually',
            'yearly'          => 'annually',
            'biennially'      => 'biennially',
            'biennial'        => 'biennially',
            'biyearly'        => 'biennially',
            'domain'          => 'domain',
        ];

        return $map[$key] ?? 'monthly';
    }
}
