from __future__ import annotations

from math import pow
from .models import SimulationRequest, ScheduleRow, Summary, SimulationResponse, InstallmentType, OverpaymentEffect


def _round_money(value: float) -> float:
    return round(max(value, 0.0) + 1e-9, 2)


def _rate_for_month(req: SimulationRequest, month: int) -> float:
    changes = sorted(req.rate_changes, key=lambda x: x.month)
    rate = req.base_rate + req.margin
    for change in changes:
        if month >= change.month:
            rate = change.annual_rate
        else:
            break
    return rate


def _monthly_payment(balance: float, monthly_rate: float, months_left: int) -> float:
    if months_left <= 0:
        return balance
    if monthly_rate == 0:
        return balance / months_left
    factor = pow(1 + monthly_rate, months_left)
    return balance * monthly_rate * factor / (factor - 1)


def _overpayment_for_month(req: SimulationRequest, month: int) -> float:
    total = 0.0
    for op in req.overpayments:
        until = op.until_month or month
        if op.repeat == "once" and month == op.month:
            total += op.amount
        elif op.repeat == "monthly" and month >= op.month and month <= until:
            total += op.amount
        elif op.repeat == "yearly" and month >= op.month and month <= until and (month - op.month) % 12 == 0:
            total += op.amount
    return total


def _simulate_raw(req: SimulationRequest, include_overpayments: bool = True) -> list[ScheduleRow]:
    balance = float(req.principal)
    total_months = int(req.years * 12)
    rows: list[ScheduleRow] = []
    fixed_payment = None
    recalculation_month = 1
    month = 1

    while balance > 0.005 and month <= total_months + 600:
        months_left = max(total_months - month + 1, 1)
        annual_rate = _rate_for_month(req, month)
        monthly_rate = annual_rate / 100 / 12
        interest = balance * monthly_rate

        if req.installment_type == InstallmentType.equal:
            if fixed_payment is None or req.overpayment_effect == OverpaymentEffect.lower_payment:
                fixed_payment = _monthly_payment(balance, monthly_rate, months_left)
            payment = fixed_payment
            principal_part = max(payment - interest, 0.0)
        else:
            principal_base = req.principal / total_months
            if req.overpayment_effect == OverpaymentEffect.lower_payment:
                active_months_left = max(total_months - month + 1, 1)
                principal_base = balance / active_months_left
            payment = principal_base + interest
            principal_part = principal_base

        if principal_part > balance:
            principal_part = balance
            payment = interest + principal_part

        balance -= principal_part

        overpayment = _overpayment_for_month(req, month) if include_overpayments else 0.0
        overpayment = min(overpayment, balance)
        balance -= overpayment

        rows.append(ScheduleRow(
            month=month,
            rate=round(annual_rate, 4),
            payment=_round_money(payment),
            principal_part=_round_money(principal_part),
            interest_part=_round_money(interest),
            overpayment=_round_money(overpayment),
            remaining=_round_money(balance),
        ))

        if include_overpayments and overpayment > 0 and req.overpayment_effect == OverpaymentEffect.lower_payment:
            fixed_payment = None
            recalculation_month = month + 1

        if include_overpayments and overpayment > 0 and req.overpayment_effect == OverpaymentEffect.shorten:
            # przy skroceniu okresu rata zostaje z grubsza taka sama; petla zakonczy sie szybciej
            pass

        month += 1
        if month - recalculation_month > 1200:
            break

    return rows


def simulate(req: SimulationRequest) -> SimulationResponse:
    actual = _simulate_raw(req, include_overpayments=True)
    baseline_req = req.model_copy(update={"overpayments": []})
    baseline = _simulate_raw(baseline_req, include_overpayments=False)

    total_interest = sum(r.interest_part for r in actual)
    total_overpayment = sum(r.overpayment for r in actual)
    total_paid = sum(r.payment + r.overpayment for r in actual)
    baseline_interest = sum(r.interest_part for r in baseline)
    payments = [r.payment for r in actual]

    summary = Summary(
        months=len(actual),
        total_paid=_round_money(total_paid),
        total_interest=_round_money(total_interest),
        total_overpayment=_round_money(total_overpayment),
        interest_saved=_round_money(baseline_interest - total_interest),
        months_saved=max(len(baseline) - len(actual), 0),
        baseline_interest=_round_money(baseline_interest),
        baseline_months=len(baseline),
        average_payment=_round_money(sum(payments) / len(payments)) if payments else 0,
        max_payment=_round_money(max(payments)) if payments else 0,
    )
    return SimulationResponse(schedule=actual, summary=summary)
