import swisseph as swe
from datetime import datetime, timedelta, date
import pytz

IST = pytz.timezone("Asia/Kolkata")


class EkadashiCalculator:
    """
    ISKCON / Vaishnava Ekadashi Calculator

    Rules:
    1. Dashami must end before Arunodaya (96 minutes before sunrise)
    2. Ekadashi must be present at Arunodaya
    3. If Ekadashi occurs on two consecutive days → observe the second day
    """

    ARUNODAYA_MINUTES = 96

    def __init__(self):
        self.tz = IST

    # -----------------------------
    # Utility: Normalize date input
    # -----------------------------
    def _normalize_date(self, d):
        if isinstance(d, str):
            return datetime.fromisoformat(d).date()

        if isinstance(d, datetime):
            return d.date()

        if isinstance(d, date):
            return d

        raise ValueError("Date must be 'YYYY-MM-DD', datetime, or date object")

    # -----------------------------
    # Calculate Tithi
    # -----------------------------
    def get_tithi(self, dt):

        utc = dt.astimezone(pytz.UTC)

        jd = swe.julday(
            utc.year,
            utc.month,
            utc.day,
            utc.hour + utc.minute / 60 + utc.second / 3600
        )

        sun = swe.calc_ut(jd, swe.SUN)[0][0]
        moon = swe.calc_ut(jd, swe.MOON)[0][0]

        angle = (moon - sun) % 360

        return int(angle / 12) + 1

    # -----------------------------
    # Sunrise (placeholder 6 AM)
    # -----------------------------
    def get_sunrise(self, d):

        d = self._normalize_date(d)

        return self.tz.localize(
            datetime(d.year, d.month, d.day, 6, 0, 0)
        )

    # -----------------------------
    # Arunodaya
    # -----------------------------
    def get_arunodaya(self, d):

        sunrise = self.get_sunrise(d)

        return sunrise - timedelta(minutes=self.ARUNODAYA_MINUTES)

    # -----------------------------
    # Dashami purity check
    # -----------------------------
    def is_dashami_pure(self, d):

        arunodaya = self.get_arunodaya(d)

        tithi_at_arunodaya = self.get_tithi(arunodaya)

        if tithi_at_arunodaya in [10, 25]:
            return False

        check_before = arunodaya - timedelta(minutes=30)
        tithi_before = self.get_tithi(check_before)

        if (tithi_before in [10, 25]) and (tithi_at_arunodaya in [11, 26]):
            return True

        return tithi_at_arunodaya not in [10, 25]

    # -----------------------------
    # Get Ekadashi dates for year
    # -----------------------------
    def get_ekadashi_dates(self, year):

        results = []

        start_date = date(year, 1, 1)
        end_date = date(year, 12, 31)

        current = start_date

        candidates = []

        while current <= end_date:

            sunrise = self.get_sunrise(current)
            arunodaya = self.get_arunodaya(current)

            tithi_sunrise = self.get_tithi(sunrise)
            tithi_arunodaya = self.get_tithi(arunodaya)

            if tithi_sunrise in [11, 26] or tithi_arunodaya in [11, 26]:

                is_pure = self.is_dashami_pure(current)

                candidates.append({
                    "date": current,
                    "tithi_sunrise": tithi_sunrise,
                    "tithi_arunodaya": tithi_arunodaya,
                    "is_pure": is_pure
                })

            current += timedelta(days=1)

        i = 0

        while i < len(candidates):

            cand = candidates[i]

            if i + 1 < len(candidates):

                next_cand = candidates[i + 1]

                if (next_cand["date"] - cand["date"]).days == 1:

                    if next_cand["is_pure"]:
                        chosen = next_cand
                    elif cand["is_pure"]:
                        chosen = cand
                    else:
                        chosen = next_cand

                    if chosen["is_pure"]:

                        sunrise = self.get_sunrise(chosen["date"])
                        tithi = self.get_tithi(sunrise)

                        paksha = "Shukla" if tithi == 11 else "Krishna"

                        results.append({
                            "date": chosen["date"].isoformat(),
                            "paksha": paksha
                        })

                    i += 2
                    continue

            if cand["is_pure"]:

                sunrise = self.get_sunrise(cand["date"])
                tithi = self.get_tithi(sunrise)

                paksha = "Shukla" if tithi == 11 else "Krishna"

                results.append({
                    "date": cand["date"].isoformat(),
                    "paksha": paksha
                })

            i += 1

        return results

    # -----------------------------
    # Check if a date is Ekadashi
    # -----------------------------
    def is_ekadashi(self, d):

        d = self._normalize_date(d)

        year = d.year

        ek_dates = self.get_ekadashi_dates(year)

        return any(e["date"] == d.isoformat() for e in ek_dates)

    # -----------------------------
    # Next Ekadashi
    # -----------------------------
    def next_ekadashi(self):

        today = datetime.now(self.tz).date()

        for year in [today.year, today.year + 1]:

            ek_dates = self.get_ekadashi_dates(year)

            for ek in ek_dates:

                ek_date = datetime.fromisoformat(ek["date"]).date()

                if ek_date > today:

                    return {
                        "date": ek["date"],
                        "paksha": ek["paksha"],
                        "days_until": (ek_date - today).days
                    }

        return None


calculator = EkadashiCalculator()


def is_ekadashi(d):
    return calculator.is_ekadashi(d)


def get_ekadashi_dates(year):
    return calculator.get_ekadashi_dates(year)


def next_ekadashi():
    return calculator.next_ekadashi()