import swisseph as swe
from datetime import datetime, timedelta
import os

# =====================================================
# SWISS EPHEMERIS PATH
# =====================================================

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
EPHE_PATH = os.path.join(BASE_DIR, "ephe")

os.environ["SE_EPHE_PATH"] = EPHE_PATH
swe.set_ephe_path(EPHE_PATH)

# =====================================================
# CONSTANTS
# =====================================================

SIGNS = [
    "Aries", "Taurus", "Gemini", "Cancer",
    "Leo", "Virgo", "Libra", "Scorpio",
    "Sagittarius", "Capricorn", "Aquarius", "Pisces"
]


# =====================================================
# BASIC HELPERS
# =====================================================

def normalize_degree(deg):
    return deg % 360


def get_rashi(longitude):
    longitude = normalize_degree(longitude)
    sign_index = int(longitude // 30)
    degree_in_sign = longitude % 30
    return SIGNS[sign_index], degree_in_sign


def format_degree(degree_in_sign):
    degree = int(degree_in_sign)
    minutes = round((degree_in_sign - degree) * 60)

    if minutes == 60:
        degree += 1
        minutes = 0

    if degree == 30:
        degree = 0

    return f"{degree}° {minutes}'"


def get_house(longitude, houses):
    longitude = normalize_degree(longitude)

    for i in range(12):
        start = normalize_degree(houses[i])
        end = normalize_degree(houses[(i + 1) % 12])

        if start < end:
            if start <= longitude < end:
                return i + 1
        else:
            # House crosses 360°
            if longitude >= start or longitude < end:
                return i + 1

    return None


def build_point_data(longitude, houses=None, retrograde=False):
    longitude = normalize_degree(longitude)
    sign, degree_in_sign = get_rashi(longitude)

    return {
        "longitude": round(longitude, 6),
        "sign": sign,
        "normal_degree": round(degree_in_sign, 4),
        "degree": format_degree(degree_in_sign),
        "house": get_house(longitude, houses) if houses else None,
        "retrograde": retrograde,
    }


def get_body_id(name, fallback=None):
    return getattr(swe, name, fallback)


def local_to_utc(year, month, day, hour, minute, second, tz):
    """
    tz examples:
    India = 5.5
    USA EST = -5
    UAE = 4
    """

    local_dt = datetime(
        int(year),
        int(month),
        int(day),
        int(hour),
        int(minute),
        int(second),
    )

    return local_dt - timedelta(hours=float(tz))


# =====================================================
# HOUSE CALCULATION FUNCTION
# =====================================================

def calculate_houses(
    jd_ut,
    latitude,
    longitude,
    house_system="P"
):
    """
    This function is required by progressed_chart.py.

    Input:
    jd_ut      = Julian Day in UT
    latitude   = birth latitude
    longitude  = birth longitude
    house_system = P means Placidus

    Output:
    houses + angles
    """

    cusps, ascmc = swe.houses(
        jd_ut,
        float(latitude),
        float(longitude),
        house_system.encode("utf-8")
    )

    ascendant = normalize_degree(ascmc[0])
    midheaven = normalize_degree(ascmc[1])
    vertex = normalize_degree(ascmc[3])

    descendant = normalize_degree(ascendant + 180)
    imum_coeli = normalize_degree(midheaven + 180)

    houses = []

    for i, cusp in enumerate(cusps, start=1):
        point = build_point_data(cusp)
        point["house"] = i
        houses.append(point)

    angles = {
        "ascendant": build_point_data(ascendant, cusps),
        "descendant": build_point_data(descendant, cusps),
        "midheaven": build_point_data(midheaven, cusps),
        "imum_coeli": build_point_data(imum_coeli, cusps),
        "vertex": build_point_data(vertex, cusps),
    }

    angles["ascendant"]["id"] = "ascendant"
    angles["ascendant"]["planet"] = "Ascendant"

    angles["descendant"]["id"] = "descendant"
    angles["descendant"]["planet"] = "Descendant"

    angles["midheaven"]["id"] = "midheaven"
    angles["midheaven"]["planet"] = "Midheaven"

    angles["imum_coeli"]["id"] = "imum_coeli"
    angles["imum_coeli"]["planet"] = "Imum Coeli"

    angles["vertex"]["id"] = "vertex"
    angles["vertex"]["planet"] = "Vertex"

    return {
        "houses": houses,
        "house_cusps": [round(normalize_degree(h), 6) for h in cusps],
        "angles": angles,
        "ascendant": angles["ascendant"],
        "midheaven": angles["midheaven"],
        "imum_coeli": angles["imum_coeli"],
        "descendant": angles["descendant"],
        "vertex": angles["vertex"],
        "ascmc": [round(normalize_degree(x), 6) for x in ascmc],
    }


# =====================================================
# MOON PHASE CALCULATION
# =====================================================

def get_moon_phase(sun_lon, moon_lon):
    """
    phase_angle = (moon_longitude - sun_longitude) % 360

    0°   = New Moon
    90°  = First Quarter Moon
    180° = Full Moon
    270° = Last Quarter Moon
    """

    angle = normalize_degree(moon_lon - sun_lon)

    phase_ranges = [
        {
            "phase": "New Moon",
            "start": 348.75,
            "end": 360.0,
            "image": "/moon/new_moon.png",
        },
        {
            "phase": "New Moon",
            "start": 0.0,
            "end": 11.25,
            "image": "/moon/new_moon.png",
        },
        {
            "phase": "Waxing Crescent Moon",
            "start": 11.25,
            "end": 78.75,
            "image": "/moon/waxing_crescent.png",
        },
        {
            "phase": "First Quarter Moon",
            "start": 78.75,
            "end": 101.25,
            "image": "/moon/first_quarter.png",
        },
        {
            "phase": "Waxing Gibbous Moon",
            "start": 101.25,
            "end": 168.75,
            "image": "/moon/waxing_gibbous.png",
        },
        {
            "phase": "Full Moon",
            "start": 168.75,
            "end": 191.25,
            "image": "/moon/full_moon.png",
        },
        {
            "phase": "Waning Gibbous Moon",
            "start": 191.25,
            "end": 258.75,
            "image": "/moon/waning_gibbous.png",
        },
        {
            "phase": "Last Quarter Moon",
            "start": 258.75,
            "end": 281.25,
            "image": "/moon/last_quarter.png",
        },
        {
            "phase": "Waning Crescent Moon",
            "start": 281.25,
            "end": 348.75,
            "image": "/moon/waning_crescent.png",
        },
    ]

    for item in phase_ranges:
        if item["start"] <= angle < item["end"]:
            return {
                "phase": item["phase"],
                "angle": round(angle, 2),
                "sun_lon": round(normalize_degree(sun_lon), 4),
                "moon_lon": round(normalize_degree(moon_lon), 4),
                "range": f'{item["start"]}° - {item["end"]}°',
                "image": item["image"],
            }

    return {
        "phase": "New Moon",
        "angle": round(angle, 2),
        "sun_lon": round(normalize_degree(sun_lon), 4),
        "moon_lon": round(normalize_degree(moon_lon), 4),
        "range": "fallback",
        "image": "/moon/new_moon.png",
    }


# =====================================================
# MAIN PLANET CALCULATION FUNCTION
# =====================================================

def calculate_planets(
    year, month, day,
    hour, minute, second,
    latitude, longitude, tz
):
    """
    Input:
    local birth date/time + place + timezone

    Output:
    planets, angles, houses, moon_phase
    """

    # -----------------------------
    # 1. Convert local birth time to UTC
    # -----------------------------
    ut_dt = local_to_utc(
        year, month, day,
        hour, minute, second,
        tz
    )

    # -----------------------------
    # 2. Julian Day
    # -----------------------------
    jd = swe.julday(
        ut_dt.year,
        ut_dt.month,
        ut_dt.day,
        ut_dt.hour + ut_dt.minute / 60 + ut_dt.second / 3600
    )

    # -----------------------------
    # 3. Houses
    # -----------------------------
    houses, ascmc = swe.houses(
        jd,
        float(latitude),
        float(longitude),
        b"P"
    )

    ascendant = normalize_degree(ascmc[0])
    mc = normalize_degree(ascmc[1])
    vertex = normalize_degree(ascmc[3])

    descendant = normalize_degree(ascendant + 180)
    ic = normalize_degree(mc + 180)

    flags = swe.FLG_SWIEPH | swe.FLG_SPEED

    result = {}
    raw_longitudes = {}

    # -----------------------------
    # 4. Bodies
    # -----------------------------
    bodies = {
        "sun": swe.SUN,
        "moon": swe.MOON,
        "mercury": swe.MERCURY,
        "venus": swe.VENUS,
        "mars": swe.MARS,
        "jupiter": swe.JUPITER,
        "saturn": swe.SATURN,
        "uranus": swe.URANUS,
        "neptune": swe.NEPTUNE,
        "pluto": swe.PLUTO,
        "north_node": swe.TRUE_NODE,
        "black_moon_lilith": swe.MEAN_APOG,
        "ceres": swe.CERES,
        "pallas": swe.PALLAS,
        "juno": swe.JUNO,
        "vesta": swe.VESTA,
        "chiron": get_body_id("CHIRON", 15),
        "pholus": get_body_id("PHOLUS", 16),
    }

    for name, body_id in bodies.items():
        data = swe.calc_ut(jd, body_id, flags)

        lon = normalize_degree(data[0][0])
        speed = data[0][3]

        raw_longitudes[name] = lon

        point = build_point_data(
            lon,
            houses,
            retrograde=speed < 0
        )

        point["id"] = name
        point["planet"] = name.replace("_", " ").title()

        result[name] = point

    # -----------------------------
    # 5. Derived points
    # -----------------------------
    result["south_node"] = build_point_data(
        normalize_degree(raw_longitudes["north_node"] + 180),
        houses
    )
    result["south_node"]["id"] = "south_node"
    result["south_node"]["planet"] = "South Node"

    result["white_moon_selena"] = build_point_data(
        normalize_degree(raw_longitudes["black_moon_lilith"] + 20.6),
        houses
    )
    result["white_moon_selena"]["id"] = "white_moon_selena"
    result["white_moon_selena"]["planet"] = "White Moon Selena"

    result["ascendant"] = build_point_data(ascendant, houses)
    result["ascendant"]["id"] = "ascendant"
    result["ascendant"]["planet"] = "Ascendant"

    result["descendant"] = build_point_data(descendant, houses)
    result["descendant"]["id"] = "descendant"
    result["descendant"]["planet"] = "Descendant"

    result["midheaven"] = build_point_data(mc, houses)
    result["midheaven"]["id"] = "midheaven"
    result["midheaven"]["planet"] = "Midheaven"

    result["imum_coeli"] = build_point_data(ic, houses)
    result["imum_coeli"]["id"] = "imum_coeli"
    result["imum_coeli"]["planet"] = "Imum Coeli"

    result["vertex"] = build_point_data(vertex, houses)
    result["vertex"]["id"] = "vertex"
    result["vertex"]["planet"] = "Vertex"

    result["part_of_fortune"] = build_point_data(
        normalize_degree(
            ascendant
            + raw_longitudes["moon"]
            - raw_longitudes["sun"]
        ),
        houses
    )
    result["part_of_fortune"]["id"] = "part_of_fortune"
    result["part_of_fortune"]["planet"] = "Part Of Fortune"

    # -----------------------------
    # 6. Moon Phase
    # -----------------------------
    moon_phase = get_moon_phase(
        raw_longitudes["sun"],
        raw_longitudes["moon"]
    )

    # -----------------------------
    # 7. Return response
    # -----------------------------
    return {
        "datetime_utc": ut_dt.isoformat(),
        "timezone_used": float(tz),

        "ascendant": result["ascendant"],
        "ascendant_sign": result["ascendant"]["sign"],
        "midheaven": result["midheaven"],
        "imum_coeli": result["imum_coeli"],
        "descendant": result["descendant"],
        "vertex": result["vertex"],

        "houses": [round(normalize_degree(h), 6) for h in houses],
        "planets": result,
        "moon_phase": moon_phase,
    }