import os
import re
import glob
from datetime import datetime, timedelta

import swisseph as swe
from kerykeion import AstrologicalSubject, KerykeionChartSVG
from chart_utils import extract_solar_return_wheel

from lunar_return_calculation import (
    ASPECTS,
    angle_diff,
    calc_body_longitude,
    calc_planets_with_houses,
    datetime_to_jd,
    decimal_to_dms,
    jd_to_local_datetime,
    local_to_ut,
)


BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CHARTS_DIR = os.path.join(BASE_DIR, "charts")
SOLAR_RETURN_CHART_DIR = os.path.join(CHARTS_DIR, "solar-return")
os.makedirs(SOLAR_RETURN_CHART_DIR, exist_ok=True)


def coordinate_to_decimal(value):
    """
    Accepts:
    22.75
    "22.75"
    "22°45' N"
    "72°18' E"
    "22° 45' N"
    "72° 18' E"

    Returns:
    22.75
    72.3
    """

    if isinstance(value, (int, float)):
        return float(value)

    text = str(value).strip().upper()

    try:
        return float(text)
    except ValueError:
        pass

    pattern = r"(\d+(?:\.\d+)?)\s*°\s*(\d+(?:\.\d+)?)?\s*'?\s*([NSEW])?"
    match = re.search(pattern, text)

    if not match:
        raise ValueError(f"Invalid coordinate format: {value}")

    degree = float(match.group(1))
    minute = float(match.group(2) or 0)
    direction = match.group(3)

    decimal = degree + minute / 60

    if direction in ["S", "W"]:
        decimal *= -1

    return round(decimal, 6)


def timezone_to_tz_str(timezone_value):
    """
    Kerykeion needs tz_str.
    Your frontend sends timezone as 5.5.
    For India use Asia/Kolkata.
    """

    try:
        tz = float(timezone_value)
    except Exception:
        return "Asia/Kolkata"

    if tz == 5.5:
        return "Asia/Kolkata"

    return "Asia/Kolkata"


def safe_filename_name(name):
    clean = "".join(
        c for c in str(name)
        if c.isalnum() or c in (" ", "-", "_")
    ).strip()

    return clean or "User"


def find_solar_return_jd(natal_sun_lon, return_year, birth_month, birth_day):
    """
    Solar Return means:
    return_year ma Sun natal Sun je exact longitude par hato,
    tyaare fari pachho ave te exact Julian Day find karvu.
    """

    birthday = datetime(return_year, birth_month, birth_day, 0, 0, 0)

    start_dt = birthday - timedelta(days=7)
    end_dt = birthday + timedelta(days=7)

    start_jd = datetime_to_jd(start_dt)
    end_jd = datetime_to_jd(end_dt)

    step = 1 / 24  # 1 hour

    best_jd = start_jd
    best_diff = 999

    jd = start_jd

    while jd <= end_jd:
        sun_lon = calc_body_longitude(jd, swe.SUN)
        diff = angle_diff(sun_lon, natal_sun_lon)

        if diff < best_diff:
            best_diff = diff
            best_jd = jd

        jd += step

    low = best_jd - (2 / 24)
    high = best_jd + (2 / 24)

    for _ in range(80):
        mid1 = low + (high - low) / 3
        mid2 = high - (high - low) / 3

        sun1 = calc_body_longitude(mid1, swe.SUN)
        sun2 = calc_body_longitude(mid2, swe.SUN)

        diff1 = angle_diff(sun1, natal_sun_lon)
        diff2 = angle_diff(sun2, natal_sun_lon)

        if diff1 < diff2:
            high = mid2
        else:
            low = mid1

    return (low + high) / 2


def get_impact(orb_value):
    if orb_value <= 0.5:
        return "Highest"
    if orb_value <= 1:
        return "Very High"
    if orb_value <= 2:
        return "High"
    if orb_value <= 4:
        return "Medium"
    return "Low"


def calculate_overlay_aspects(return_planets, natal_planets):
    aspects = []

    for rp in return_planets:
        for np in natal_planets:
            diff = angle_diff(rp["longitude"], np["longitude"])

            for aspect_name, asp in ASPECTS.items():
                orb_value = abs(diff - asp["angle"])

                if orb_value <= asp["orb"]:
                    aspects.append({
                        "return_planet": rp["name"],
                        "return_symbol": rp.get("symbol", ""),
                        "return_house": rp.get("house"),

                        "natal_planet": np["name"],
                        "natal_symbol": np.get("symbol", ""),
                        "natal_house": np.get("house"),

                        "aspect": aspect_name,
                        "aspect_symbol": asp["symbol"],
                        "aspect_type": asp["type"],

                        "orb": decimal_to_dms(orb_value),
                        "orb_value": round(orb_value, 4),

                        "effect": asp.get("effect", ""),
                        "impact": get_impact(orb_value),
                    })

    aspects.sort(key=lambda x: x["orb_value"])
    return aspects


def generate_solar_return_svg(
    payload,
    return_local_dt,
    birth_latitude,
    birth_longitude,
    return_latitude,
    return_longitude,
):
    safe_name = safe_filename_name(payload.name)

    birth_tz_str = timezone_to_tz_str(payload.timezone)
    return_tz_str = timezone_to_tz_str(payload.return_timezone)

    natal_subject = AstrologicalSubject(
        name=f"{safe_name}_Natal",
        year=payload.year,
        month=payload.month,
        day=payload.day,
        hour=payload.hour,
        minute=payload.minute,
        city=payload.birth_place or "Birth Place",
        nation="IN",
        lat=birth_latitude,
        lng=birth_longitude,
        tz_str=birth_tz_str,
    )

    solar_subject = AstrologicalSubject(
        name=f"{safe_name}_Solar_Return_{payload.return_year}",
        year=return_local_dt.year,
        month=return_local_dt.month,
        day=return_local_dt.day,
        hour=return_local_dt.hour,
        minute=return_local_dt.minute,
        city=payload.return_place or payload.birth_place or "Return Place",
        nation="IN",
        lat=return_latitude,
        lng=return_longitude,
        tz_str=return_tz_str,
    )

    before_files = set(glob.glob(os.path.join(SOLAR_RETURN_CHART_DIR, "*.svg")))

    chart = KerykeionChartSVG(
        first_obj=natal_subject,
        chart_type="Transit",
        second_obj=solar_subject,
        new_output_directory=SOLAR_RETURN_CHART_DIR,
    )

    chart.makeSVG()

    after_files = set(glob.glob(os.path.join(SOLAR_RETURN_CHART_DIR, "*.svg")))
    new_files = list(after_files - before_files)

    if new_files:
        latest_file = max(new_files, key=os.path.getmtime)
    else:
        svg_files = glob.glob(os.path.join(SOLAR_RETURN_CHART_DIR, "*.svg"))

        if not svg_files:
            raise RuntimeError("Solar Return SVG chart was not generated.")

        latest_file = max(svg_files, key=os.path.getmtime)

    base_name = os.path.splitext(os.path.basename(latest_file))[0]
    wheel_filename = f"{base_name}_wheel.svg"
    wheel_path = os.path.join(SOLAR_RETURN_CHART_DIR, wheel_filename)

    ok = extract_solar_return_wheel(latest_file, wheel_path)

    if ok:
        return f"/charts/solar-return/{wheel_filename}"

    filename = os.path.basename(latest_file)
    return f"/charts/solar-return/{filename}"


def calculate_solar_return(payload):
    birth_latitude = coordinate_to_decimal(payload.latitude)
    birth_longitude = coordinate_to_decimal(payload.longitude)

    return_latitude = coordinate_to_decimal(payload.return_latitude)
    return_longitude = coordinate_to_decimal(payload.return_longitude)

    print("BIRTH LAT/LONG:", birth_latitude, birth_longitude)
    print("RETURN LAT/LONG:", return_latitude, return_longitude)

    birth_ut = local_to_ut(
        payload.year,
        payload.month,
        payload.day,
        payload.hour,
        payload.minute,
        payload.second,
        payload.timezone,
    )

    birth_jd = datetime_to_jd(birth_ut)

    natal_planets = calc_planets_with_houses(
        birth_jd,
        birth_latitude,
        birth_longitude,
    )

    natal_sun = next(
        (p for p in natal_planets if p["name"].lower() == "sun"),
        None
    )

    if not natal_sun:
        raise ValueError("Natal Sun not found.")

    natal_sun_lon = natal_sun["longitude"]

    solar_return_jd = find_solar_return_jd(
        natal_sun_lon=natal_sun_lon,
        return_year=payload.return_year,
        birth_month=payload.month,
        birth_day=payload.day,
    )

    return_local_dt = jd_to_local_datetime(
        solar_return_jd,
        payload.return_timezone,
    )

    solar_return_planets = calc_planets_with_houses(
        solar_return_jd,
        return_latitude,
        return_longitude,
    )

    overlay_aspects = calculate_overlay_aspects(
        solar_return_planets,
        natal_planets,
    )

    major_overlay_aspects = [
        a for a in overlay_aspects
        if a["aspect_type"] == "Major"
    ]

    minor_overlay_aspects = [
        a for a in overlay_aspects
        if a["aspect_type"] == "Minor"
    ]

    chart_url = generate_solar_return_svg(
        payload=payload,
        return_local_dt=return_local_dt,
        birth_latitude=birth_latitude,
        birth_longitude=birth_longitude,
        return_latitude=return_latitude,
        return_longitude=return_longitude,
    )

    return {
        "chart_type": "solar_return",
        "chart_url": chart_url,

        "details": {
            "name": payload.name,

            "birth_date": f"{payload.year}-{payload.month:02d}-{payload.day:02d}",
            "birth_time": f"{payload.hour:02d}:{payload.minute:02d}",
            "birth_place": payload.birth_place,
            "birth_latitude": birth_latitude,
            "birth_longitude": birth_longitude,
            "birth_timezone": payload.timezone,

            "house_system": "Placidus",

            "return_date": return_local_dt.strftime("%Y-%m-%d"),
            "return_time": return_local_dt.strftime("%H:%M"),
            "return_place": payload.return_place or payload.birth_place,
            "return_latitude": return_latitude,
            "return_longitude": return_longitude,
            "return_timezone": payload.return_timezone,
        },

        "section_1_details": {
            "title": "Details of Your Solar Return",

            "birth_details": {
                "birth_date": f"{payload.year}-{payload.month:02d}-{payload.day:02d}",
                "birth_time": f"{payload.hour:02d}:{payload.minute:02d}",
                "location": payload.birth_place,
                "planet_color": "INDIGO",
            },

            "chart_details": {
                "house_system": "Placidus",
            },

            "solar_return_details": {
                "return_date": return_local_dt.strftime("%Y-%m-%d"),
                "return_time": return_local_dt.strftime("%H:%M"),
                "location": payload.return_place or payload.birth_place,
                "planet_color": "PINK",
            },
        },

        "section_2_planet_positions": {
            "title": "Planet Positions",
            "natal_planets": natal_planets,
            "solar_return_planets": solar_return_planets,
        },

        "section_3_overlay_aspects": {
            "title": "Solar Return Overlay Aspects",
            "all": overlay_aspects,
            "major": major_overlay_aspects,
            "minor": minor_overlay_aspects,
        },

        "natal_planets": natal_planets,
        "solar_return_planets": solar_return_planets,
        "overlay_aspects": overlay_aspects,
    }