"""
Core ephemeris calculation logic — shared by views and management commands.
"""
from django.conf import settings as django_settings
import swisseph as swe


DEFAULT_HOUSE_SYSTEM = 'O'  # Porphyry

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

SIGN_ELEMENT = {
    'Aries': 'Fire',  'Leo': 'Fire',   'Sagittarius': 'Fire',
    'Taurus': 'Earth', 'Virgo': 'Earth', 'Capricorn': 'Earth',
    'Gemini': 'Air',  'Libra': 'Air',  'Aquarius': 'Air',
    'Cancer': 'Water', 'Scorpio': 'Water', 'Pisces': 'Water',
}

ASPECTS = [
    ('Conjunction',      0,   8.0),
    ('Semisextile',     30,   4.0),
    ('Semisquare',      45,   4.0),
    ('Sextile',         60,   6.0),
    ('Square',          90,   8.0),
    ('Trine',          120,   8.0),
    ('Sesquiquadrate', 135,   4.0),
    ('Quincunx',       150,   5.0),
    ('Opposition',     180,  10.0),
]

PLANET_CODES = {
    '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,
}


def set_ephe_path():
    ephe_path = getattr(django_settings, 'SWISSEPH_PATH', None)
    if ephe_path:
        swe.set_ephe_path(ephe_path)


def get_sign(lon):
    return SIGNS[int(lon // 30) % 12]


def get_julian_day(dt):
    return swe.julday(
        dt.year, dt.month, dt.day,
        dt.hour + dt.minute / 60 + dt.second / 3600,
    )


def get_planet_positions(jd):
    flag = swe.FLG_SWIEPH | swe.FLG_SPEED
    planets = {}
    for name, code in PLANET_CODES.items():
        pos, _ = swe.calc_ut(jd, code, flag)
        degree = pos[0]
        planets[name] = {
            'sign': get_sign(degree),
            'degree': degree,
            'speed': pos[3],
            'retrograde': pos[3] < 0,
        }
    return planets


def get_element_counts(planets):
    sign_counts = {s: 0 for s in SIGNS}
    sign_planets = {s: [] for s in SIGNS}
    classic = {'Fire': [], 'Water': [], 'Earth': [], 'Air': []}

    for name, data in planets.items():
        sign = data['sign']
        el = SIGN_ELEMENT[sign]
        classic[el].append({'planet': name, 'sign': sign})
        sign_counts[sign] += 1
        sign_planets[sign].append({'planet': name, 'sign': sign})

    result = {
        el: {'count': len(contribs), 'contributors': contribs}
        for el, contribs in classic.items()
    }

    # Time: stellium — highest concentration in one sign, bonus = size - 1.
    # Collect all signs tied at the maximum.
    max_in_sign = max(sign_counts.values())
    stellia = [
        {'sign': s, 'planets': sign_planets[s]}
        for s in SIGNS
        if sign_counts[s] == max_in_sign and max_in_sign > 1
    ]
    result['Time'] = {
        'count': max_in_sign - 1,
        'stellia': stellia,
    }

    # Space: parade — longest consecutive run of occupied signs (circular),
    # bonus = run length - 1. Collect all runs tied at the maximum.
    index_set = {i for i, s in enumerate(SIGNS) if sign_counts[s] > 0}
    indices = sorted(index_set)
    max_seq = 0
    for start in range(len(indices)):
        seq_len = 1
        for offset in range(1, len(indices)):
            if (indices[start] + offset) % len(SIGNS) in index_set:
                seq_len += 1
            else:
                break
        max_seq = max(max_seq, seq_len)

    parades = []
    for start in range(len(indices)):
        run = []
        for offset in range(max_seq):
            idx = (indices[start] + offset) % len(SIGNS)
            if idx not in index_set:
                break
            run.append(idx)
        else:
            sign_run = [SIGNS[i] for i in run]
            parade_planets = [
                p for s in sign_run for p in sign_planets[s]
            ]
            parades.append({'signs': sign_run, 'planets': parade_planets})

    result['Space'] = {
        'count': max_seq - 1,
        'parades': parades,
    }

    return result


def calculate_aspects(planets):
    """Return a list of aspects between all planet pairs.

    Each entry: {planet1, planet2, type, angle (actual, rounded), orb (rounded)}.
    Only the first matching aspect type is reported per pair (aspects are
    well-separated enough that at most one can apply with standard orbs).
    """
    names = list(planets.keys())
    aspects = []
    for i, name1 in enumerate(names):
        for name2 in names[i + 1:]:
            deg1 = planets[name1]['degree']
            deg2 = planets[name2]['degree']
            angle = abs(deg1 - deg2)
            if angle > 180:
                angle = 360 - angle
            for aspect_name, target, max_orb in ASPECTS:
                orb = abs(angle - target)
                if orb <= max_orb:
                    s1 = abs(planets[name1].get('speed', 0))
                    s2 = abs(planets[name2].get('speed', 0))
                    applying = name1 if s1 >= s2 else name2
                    aspects.append({
                        'planet1': name1,
                        'planet2': name2,
                        'type': aspect_name,
                        'angle': round(angle, 2),
                        'orb': round(orb, 2),
                        'applying_planet': applying,
                    })
                    break
    return aspects
