"""Kenyan statutory payroll deductions.

The rates are configuration, not law, and they change (SHIF replaced NHIF,
NSSF moved to a tiered model, the housing levy was introduced, and the
deductibility of each against PAYE shifted with them). Each company stores its
own editable rate set; the constants here are only the starting defaults.
Every payroll run snapshots the rates it used, so an old payslip can always be
explained even after the rates are changed.
"""

from decimal import ROUND_HALF_UP, Decimal

# Defaults a new company starts from. Bands are (upper limit, rate); a null
# upper limit means "everything above the previous band".
DEFAULT_PAYE_BANDS = [
    {'up_to': '24000', 'rate': '0.10'},
    {'up_to': '32333', 'rate': '0.25'},
    {'up_to': '500000', 'rate': '0.30'},
    {'up_to': '800000', 'rate': '0.325'},
    {'up_to': None, 'rate': '0.35'},
]

DEFAULT_RATES = {
    'paye_bands': DEFAULT_PAYE_BANDS,
    'personal_relief': '2400',
    'nssf_rate': '0.06',
    'nssf_tier_1_limit': '7000',
    'nssf_tier_2_limit': '36000',
    'shif_rate': '0.0275',
    'shif_minimum': '300',
    'housing_levy_rate': '0.015',
    'nssf_deductible_for_paye': True,
    'shif_deductible_for_paye': True,
    'housing_levy_deductible_for_paye': True,
}


def _d(value) -> Decimal:
    return value if isinstance(value, Decimal) else Decimal(str(value))


def _money(value) -> Decimal:
    return _d(value).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)


def compute_nssf(gross, rates) -> Decimal:
    gross = _d(gross)
    rate = _d(rates['nssf_rate'])
    tier_1_limit = _d(rates['nssf_tier_1_limit'])
    tier_2_limit = _d(rates['nssf_tier_2_limit'])

    tier_1 = min(gross, tier_1_limit) * rate
    tier_2 = max(Decimal('0'), min(gross, tier_2_limit) - tier_1_limit) * rate
    return _money(tier_1 + tier_2)


def compute_shif(gross, rates) -> Decimal:
    return _money(max(_d(gross) * _d(rates['shif_rate']), _d(rates['shif_minimum'])))


def compute_housing_levy(gross, rates) -> Decimal:
    return _money(_d(gross) * _d(rates['housing_levy_rate']))


def compute_paye(taxable_income, rates) -> Decimal:
    """Progressive PAYE on taxable income, net of personal relief."""
    remaining = max(Decimal('0'), _d(taxable_income))
    tax = Decimal('0')
    previous_limit = Decimal('0')

    for band in rates['paye_bands']:
        if remaining <= 0:
            break
        upper = band.get('up_to')
        rate = _d(band['rate'])
        if upper in (None, ''):
            band_width = remaining
        else:
            band_width = _d(upper) - previous_limit
            previous_limit = _d(upper)
        taxed_here = min(remaining, max(Decimal('0'), band_width))
        tax += taxed_here * rate
        remaining -= taxed_here

    return _money(max(Decimal('0'), tax - _d(rates['personal_relief'])))


def compute_payslip(basic_salary, allowances=0, other_deductions=0, rates=None) -> dict:
    """Full monthly breakdown for one employee under the given rate set."""
    rates = rates or DEFAULT_RATES

    basic = _d(basic_salary)
    allow = _d(allowances or 0)
    other = _d(other_deductions or 0)
    gross = _money(basic + allow)

    nssf = compute_nssf(gross, rates)
    shif = compute_shif(gross, rates)
    housing_levy = compute_housing_levy(gross, rates)

    pre_tax_relief = Decimal('0')
    if rates.get('nssf_deductible_for_paye', True):
        pre_tax_relief += nssf
    if rates.get('shif_deductible_for_paye', True):
        pre_tax_relief += shif
    if rates.get('housing_levy_deductible_for_paye', True):
        pre_tax_relief += housing_levy

    taxable_income = max(Decimal('0'), gross - pre_tax_relief)
    paye = compute_paye(taxable_income, rates)

    total_deductions = _money(paye + nssf + shif + housing_levy + other)
    net_pay = _money(gross - total_deductions)

    return {
        'basic_salary': _money(basic),
        'allowances': _money(allow),
        'gross_pay': gross,
        'taxable_income': _money(taxable_income),
        'paye': paye,
        'nssf': nssf,
        'shif': shif,
        'housing_levy': housing_levy,
        'other_deductions': _money(other),
        'total_deductions': total_deductions,
        'net_pay': net_pay,
    }


def rates_to_dict(obj) -> dict:
    """Turn a PayrollRates row into the plain dict the engine expects."""
    return {
        'paye_bands': obj.paye_bands or DEFAULT_PAYE_BANDS,
        'personal_relief': str(obj.personal_relief),
        'nssf_rate': str(obj.nssf_rate),
        'nssf_tier_1_limit': str(obj.nssf_tier_1_limit),
        'nssf_tier_2_limit': str(obj.nssf_tier_2_limit),
        'shif_rate': str(obj.shif_rate),
        'shif_minimum': str(obj.shif_minimum),
        'housing_levy_rate': str(obj.housing_levy_rate),
        'nssf_deductible_for_paye': obj.nssf_deductible_for_paye,
        'shif_deductible_for_paye': obj.shif_deductible_for_paye,
        'housing_levy_deductible_for_paye': obj.housing_levy_deductible_for_paye,
    }
