from datetime import datetime
from calendar import isleap
from sqlalchemy import Integer, DateTime, ForeignKey, Float, Boolean, String
from sqlalchemy.orm import relationship, Mapped, mapped_column
from src.apps.base.models.base import Base
from sqlalchemy.ext.hybrid import hybrid_property
from src.apps.payment_requests.enums import (
    RecurringPaymentEndTypes,
    RecurringPaymentIntervals,
    RecurringPaymentRepeatTypes,
)
from typing import Optional


def _round_half_up(number, decimals: int = 0) -> int:
    """
    This function rounds a number half up. Eg. 18.5 will round to 19
    """
    rounded_value = int(number * (10**decimals) + 0.5) / (10**decimals)
    if rounded_value % 1 == 0:
        rounded_value = int(rounded_value)
    return rounded_value


class RecurringPaymentRequests(Base):
    """
    Recurring Payment Request Model: ORM class for Recurring Payment Request Entity
    """

    __tablename__ = "recurring_payment_requests"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True, autoincrement=True)
    prorate_first_payment: Mapped[bool] = mapped_column(Boolean, default=False)
    interval: Mapped[str] = mapped_column(String(50), default=RecurringPaymentIntervals.MONTH)
    interval_value: Mapped[int] = mapped_column(Integer, default=1)
    interval_time: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
    start_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    prorate_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    next_due_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    next_due_amount: Mapped[float] = mapped_column(Float, default=0.0)
    repeat_type: Mapped[str] = mapped_column(String(50), default=RecurringPaymentRepeatTypes.ON_DATE)
    end_type: Mapped[str] = mapped_column(String(50), default=RecurringPaymentEndTypes.UNTIL_CANCELLED)
    end_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
    pay_until_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
    paid_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
    is_autopay: Mapped[bool] = mapped_column(Boolean, default=False)
    autopay_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
    autopay_interval: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
    checkout_interval: Mapped[str] = mapped_column(String(50), default=RecurringPaymentIntervals.MONTH)
    checkout_interval_value: Mapped[int] = mapped_column(Integer, default=1)
    checkout_interval_time: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
    checkout_repeat_type: Mapped[str] = mapped_column(
        String(50), default=RecurringPaymentRepeatTypes.ON_DATE
    )

    payment_request_id: Mapped[int] = mapped_column(Integer, ForeignKey("payment_requests.id"))
    payment_request: Mapped["PaymentRequest"] = relationship("PaymentRequest")

    @hybrid_property
    def prorate_amount(self) -> int:
        """
        A property to check if sms notifications are enabled for this payment request
        """
        total_amount: float = self.payment_request.amount
        total_due: float = 0.0
        amount_per_day: float = 0.0
        today: datetime = datetime.utcnow()
        days_in_current_year: int = 366 if isleap(today.year) else 365
        _round = lambda x: round(x, 2)
        try:
            # Check repeat types
            ######## DAY_OF_MONTH #########
            if self.repeat_type == RecurringPaymentRepeatTypes.DAY_OF_MONTH:
                one_day = _round(days_in_current_year / 12)
                amount_per_day = _round_half_up(total_amount / one_day)

            if self.repeat_type == RecurringPaymentRepeatTypes.TIME_OF_MONTH:
                one_day = _round(days_in_current_year / 12)
                amount_per_day = _round_half_up(total_amount / one_day)

            if self.repeat_type == RecurringPaymentRepeatTypes.INTERVAL:
                if self.interval == RecurringPaymentIntervals.DAY:
                    amount_per_day = _round_half_up(total_amount)

                elif self.interval == RecurringPaymentIntervals.WEEK:
                    amount_per_day = _round_half_up(total_amount / 7)

                elif self.interval == RecurringPaymentIntervals.MONTH:
                    one_day = _round(days_in_current_year / 12)
                    amount_per_day = _round_half_up(total_amount / one_day)

                elif self.interval == RecurringPaymentIntervals.QUARTER:
                    one_day = _round(days_in_current_year / 4)
                    amount_per_day = _round_half_up(total_amount / one_day)

                elif self.interval == RecurringPaymentIntervals.YEAR:
                    amount_per_day = _round_half_up(total_amount / days_in_current_year)

            if self.repeat_type == RecurringPaymentRepeatTypes.CUSTOM_INTERVAL:
                interval_value = self.interval_value
                if self.interval == RecurringPaymentIntervals.DAY:
                    amount_per_day = _round_half_up(total_amount / interval_value)

                elif self.interval == RecurringPaymentIntervals.WEEK:
                    amount_per_day = _round_half_up(total_amount / (7 * interval_value))

                elif self.interval == RecurringPaymentIntervals.MONTH:
                    one_day = _round(days_in_current_year / 12)
                    amount_per_day = _round_half_up(
                        total_amount / (one_day * interval_value)
                    )

                elif self.interval == RecurringPaymentIntervals.QUARTER:
                    one_day = _round(days_in_current_year / 4)
                    amount_per_day = _round_half_up(
                        total_amount / (one_day * interval_value)
                    )

                elif self.interval == RecurringPaymentIntervals.YEAR:
                    amount_per_day = _round_half_up(
                        total_amount / (days_in_current_year * interval_value)
                    )

            total_due = _round_half_up(
                amount_per_day
                * (self.start_date.date() - self.prorate_date.date()).days
            )
            return total_due
        except Exception as e:
            return 0
