import datetime
import math
from functools import lru_cache

import pandas as pd


def monthly_fourier_features(timestamps: pd.Series, month_fourier_degree: int = 2):
    if len(timestamps):
        data = pd.DataFrame({"time": timestamps})
        month_pos = timestamps.apply(lambda x: position_in_month(datetime.date(x.year, x.month, x.day)))
        for d in range(month_fourier_degree):
            data[f"cos{d+1}"] = (2 * (d + 1) * math.pi * month_pos).apply(math.cos)
            data[f"sin{d + 1}"] = (2 * (d + 1) * math.pi * month_pos).apply(math.sin)

        drop_cols = ["time"]
        data = data.drop(columns=drop_cols)
        return data
    else:
        columns = []
        for d in range(month_fourier_degree):
            columns += [f"cos{d+1}", f"sin{d + 1}"]

        return pd.DataFrame(columns=columns)


@lru_cache(maxsize=4096)
def position_in_month(d: datetime.date):
    prev = datetime.date(d.year, d.month, 1) - datetime.timedelta(days=1)
    nxt = datetime.date(
        d.year + 1 if d.month == 12 else d.year, 1 if d.month == 12 else d.month + 1, 1
    ) - datetime.timedelta(days=1)
    delta = (d - prev).days / (nxt - prev).days
    return delta
