# SPDX-License-Identifier: MIT
"""AlphaBot's Decimal-only monthly momentum policy and permanent floor ratchet."""
from decimal import Decimal, localcontext

D = Decimal


def decimal(value):
    if isinstance(value, (float, bool)):
        raise ValueError('economic inputs must be decimal strings, integers or Decimal')
    value = D(value)
    if not value.is_finite():
        raise ValueError('nonfinite economic input')
    return value


def capital_floor(starting_capital, asset_cost, peak_equity, previous_floor='250'):
    """Retain the original acquisition-cost protection AND permanent milestone floor.

    Asset cost includes entry fees (or a disclosed conservative fee reserve).
    $1k/$2k/$4k peaks establish $500/$1k/$2k milestone floors. The permanent
    milestone ratchet is distinct from the changing starting-capital-minus-cost term.
    previous_floor must be the persisted milestone floor, not that changing term.
    """
    start, cost, peak, previous = map(decimal,
        (starting_capital, asset_cost, peak_equity, previous_floor))
    if start < 0 or cost < 0 or peak < 0 or previous < 250:
        raise ValueError('invalid floor inputs')
    milestone, ratchet = D('1000'), max(D('250'), previous)
    while peak >= milestone:
        ratchet = max(ratchet, milestone / 2)
        milestone *= 2
    return max(ratchet, start - cost), ratchet


def risk_multiplier(lesson_count, decay='0.95'):
    decay = decimal(decay)
    if type(lesson_count) is not int or lesson_count < 0 or not 0 < decay < 1:
        raise ValueError('invalid lesson decay/count')
    with localcontext() as ctx:
        ctx.prec = 50
        return decay ** lesson_count


def target_deviation(reference, fill, target_multiplier='1'):
    reference, fill, multiplier = map(decimal, (reference, fill, target_multiplier))
    if min(reference, fill, multiplier) <= 0:
        raise ValueError('target and fill must be positive')
    target = reference * multiplier
    return target, abs(fill - target) / target * 100


def decide(context):
    """No signal is invented when monthly data is unavailable."""
    tone = context.get('tone')
    if tone is not None and decimal(tone) < -5:
        return {'action': 'liquidate', 'reason': 'geopolitical_tone_below_minus_five'}
    if tone is None:
        return {'action': 'hold', 'reason': 'geopolitical_tone_unknown'}
    values = [decimal(v) for v in context.get('indpro', [])]
    if len(values) != 4:
        return {'action': 'hold', 'reason': 'four_consecutive_visible_INDPRO_months_required'}
    if context.get('month') == context.get('last_entry_month'):
        return {'action': 'hold', 'reason': 'monthly_entry_already_consumed'}
    mean = sum(values[:3], D('0')) / 3
    multiplier = risk_multiplier(context['lesson_count'], context['lesson_risk_decay'])
    if values[3] <= mean:
        return {'action': 'hold', 'reason': 'INDPRO_not_above_prior_three_mean'}
    budget = decimal(context['cash']) * D('0.20') * multiplier
    if budget <= 0:
        return {'action': 'hold', 'reason': 'no_positive_cash_budget'}
    return {'action': 'buy', 'budget': budget,
            'reason': f'INDPRO_current={values[3]} > prior_three_mean={mean}; budget={decimal(context["cash"])} * 0.20 * risk_multiplier={multiplier} = {budget}', 'prior_mean': mean,
            'risk_multiplier': multiplier}
