<?php

namespace App\Services\Payroll;

use App\Models\TaxTable;
use InvalidArgumentException;

/**
 * Resolves the PAYE strategy for a payroll run by ruleset code and effective
 * date. The graduated CRA-based calculation (PitaPayeStrategy) is configured
 * from the matching TaxTable, so the bands and reliefs are maintained from the
 * UI while historical runs stay reproducible. Codes without a configured table
 * fall back to the strategy's built-in constants.
 */
class PayeStrategyFactory
{
    /** Codes handled by the graduated CRA-based calculation. */
    private const GRADUATED = ['PITA'];

    public function make(string $code, ?string $date = null): PayeStrategy
    {
        $code = strtoupper($code);

        $table = $this->resolveTable($code, $date ?? now()->toDateString());
        if ($table !== null) {
            return $this->fromTable($table);
        }

        // No configured table: fall back to built-in defaults for known codes.
        if (in_array($code, self::GRADUATED, true)) {
            return new PitaPayeStrategy(code: $code);
        }

        throw new InvalidArgumentException("Unknown PAYE ruleset: {$code}");
    }

    public function default(): PayeStrategy
    {
        return $this->make('PITA');
    }

    private function resolveTable(string $code, string $date): ?TaxTable
    {
        $table = TaxTable::effectiveOn($code, $date)->with('bands')->first();

        // If the run predates every configured window, use the earliest active
        // table for the code rather than falling through to hardcoded defaults.
        if ($table === null) {
            $table = TaxTable::where('code', $code)->where('active', true)
                ->with('bands')->orderBy('effective_from')->first();
        }

        return $table !== null && $table->bands->isNotEmpty() ? $table : null;
    }

    private function fromTable(TaxTable $table): PitaPayeStrategy
    {
        $bands = $table->bands
            ->map(fn ($b) => [$b->width !== null ? (float) $b->width : null, (float) $b->rate])
            ->all();

        return new PitaPayeStrategy(
            bands: $bands,
            craFixedMin: (float) $table->cra_fixed_min,
            craAltPercent: (float) $table->cra_alt_percent,
            craGrossPercent: (float) $table->cra_gross_percent,
            minTaxPercent: (float) $table->min_tax_percent,
            code: $table->code,
        );
    }
}
