from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from enum import Enum

from moneyed import Money

DATE_FORMAT = "%Y-%m-%d"


class Mark(Enum):
    UNMARKED = ""
    PENDING = "!"
    CLEARED = "*"


@dataclass
class Posting:
    account: list[str]
    amount: Money
    comment: str


@dataclass
class Entry:
    date: date
    status: Mark
    code: str
    description: str
    comment: str
    postings: list[Posting]

    @property
    def to_str(self):
        result: str = str()

        result = f"{self.date.strftime(format=DATE_FORMAT)} {self.description}"

        if self.comment:
            result += f" ; {self.comment}"

        for post in self.postings:
            temp = str(post.amount).replace("-$", "$-")
            result += f"\n   {':'.join(post.account)}  {temp}"

            if post.comment:
                result += f" ; {post.comment}"

        return result


def add_month(current_date: date) -> date:
    year = current_date.year
    month = current_date.month
    day = current_date.day

    month = month + 1
    if month > 12:
        year += 1
        month = 1

    return date(year, month, day)


@dataclass
class Payment:
    beginning_principal: Money
    term: date
    interest: Money
    principal: Money
    ending_principal: Money

    @property
    def amount(self):
        return self.interest + self.principal

    def __repr__(self):
        return (
            "<Payment("
            f"term={self.term.strftime(format=DATE_FORMAT)}, "
            f"begin={self.beginning_principal}, "
            f"pmt_i={self.interest}, "
            f"pmt_p={self.principal}, "
            f"total={self.amount}, "
            f"end={self.ending_principal}"
            ")>"
        )


@dataclass
class Loan:
    name: str
    begin: date
    term_months: int
    annual_interest: Decimal
    principal: Money

    def __post_init__(self):
        self.interest = self.annual_interest / 12
        one_plus_term = (1 + self.interest) ** (self.term_months)
        self.payment = self.principal * self.interest * one_plus_term / (one_plus_term - 1)

    def payment_schedule(self) -> dict[date, Payment]:
        payments: dict[date, Payment] = dict()
        current: date = self.begin

        while self.principal > Money(0, "USD"):
            interest_pmt = self.principal * self.interest
            principal_pmt = self.payment - interest_pmt

            if principal_pmt > self.principal:
                principal_pmt = self.principal

            beginning_principal = self.principal
            ending_principal = self.principal - principal_pmt

            payments[current] = Payment(
                beginning_principal=beginning_principal.round(2),
                term=current,
                interest=interest_pmt.round(2),
                principal=principal_pmt.round(2),
                ending_principal=ending_principal.round(2),
            )

            self.principal -= principal_pmt
            current = add_month(current)

        return payments
