"""House system calculation engines."""

from dataclasses import replace

import swisseph as swe

from stellium.core.models import (
    CelestialPosition,
    ChartDateTime,
    ChartLocation,
    HouseCusps,
    ObjectType,
)
from stellium.utils.cache import cached

# Swiss Ephemeris house system codes
HOUSE_SYSTEM_CODES = {
    "Alcabitius": b"B",
    "APC": b"Y",
    "Axial Rotation": b"X",
    "Campanus": b"C",
    "Equal": b"A",
    "Equal (MC)": b"D",
    "Equal (Vertex)": b"E",
    "Gauquelin": b"G",
    "Horizontal": b"H",
    "Koch": b"K",
    "Krusinski": b"U",
    "Morinus": b"M",
    "Placidus": b"P",
    "Porphyry": b"O",
    "Regiomontanus": b"R",
    "Topocentric": b"T",
    "Vehlow Equal": b"V",
    "Whole Sign": b"W",
}


class SwissHouseSystemBase:
    """
    Provides a default implementation for calling swisseph and assigning houses.

    This is NOT a protocol, just a helper class for code reuse.
    """

    @property
    def system_name(self) -> str:
        return "BaseClass"

    @cached(cache_type="ephemeris", max_age_seconds=86400)
    def _calculate_swiss_houses(
        self, julian_day: float, latitude: float, longitude: float, system_code: bytes
    ) -> tuple:
        """Cached Swiss Ephemeris house calculation."""
        return swe.houses(julian_day, latitude, longitude, hsys=system_code)

    def assign_houses(
        self, positions: list[CelestialPosition], cusps: HouseCusps
    ) -> dict[str, int]:
        """Assign house numbers to positions. Returns a simple name: house dict."""
        placements = {}
        for pos in positions:
            house_num = self._find_house(pos.longitude, cusps.cusps)
            placements[pos.name] = house_num
        return placements

    def _find_house(self, longitude: float, cusps: tuple) -> int:
        """Find which house a longitude falls into."""
        cusp_list = list(cusps)

        for i in range(12):
            cusp1 = cusp_list[i]
            cusp2 = cusp_list[(i + 1) % 12]

            # Handles wrapping about 360 degrees
            if cusp2 < cusp1:
                cusp2 += 360
                test_long = longitude if longitude >= cusp1 else longitude + 360
            else:
                test_long = longitude

            if cusp1 <= test_long < cusp2:
                return i + 1

        return 1  # fallback

    def calculate_house_data(
        self, datetime: ChartDateTime, location: ChartLocation
    ) -> tuple[HouseCusps, list[CelestialPosition]]:
        """Calculate house system's house cusps and chart angles."""
        # Cusps
        cusps_list, angles_list = self._calculate_swiss_houses(
            datetime.julian_day,
            location.latitude,
            location.longitude,
            HOUSE_SYSTEM_CODES[self.system_name],
        )
        cusps = HouseCusps(system=self.system_name, cusps=tuple(cusps_list))

        # Chart angles
        asc = angles_list[0]
        mc = angles_list[1]
        vertex = angles_list[3]

        angles = [
            CelestialPosition(name="ASC", object_type=ObjectType.ANGLE, longitude=asc),
            CelestialPosition(name="MC", object_type=ObjectType.ANGLE, longitude=mc),
            # Derive Dsc and IC
            CelestialPosition(
                name="DSC", object_type=ObjectType.ANGLE, longitude=(asc + 180) % 360
            ),
            CelestialPosition(
                name="IC", object_type=ObjectType.ANGLE, longitude=(mc + 180) % 360
            ),
            # Include Vertex
            CelestialPosition(
                name="Vertex", object_type=ObjectType.POINT, longitude=vertex
            ),
        ]

        return cusps, angles


class PlacidusHouses(SwissHouseSystemBase):
    """Placidus house system engine."""

    @property
    def system_name(self) -> str:
        return "Placidus"


class WholeSignHouses(SwissHouseSystemBase):
    """Whole sign house system engine."""

    @property
    def system_name(self) -> str:
        return "Whole Sign"


class KochHouses(SwissHouseSystemBase):
    """Koch house system engine."""

    @property
    def system_name(self) -> str:
        return "Koch"


class EqualHouses(SwissHouseSystemBase):
    """Equal house system engine."""

    @property
    def system_name(self) -> str:
        return "Equal"


class PorphyryHouses(SwissHouseSystemBase):
    """Porphyry house system engine."""

    @property
    def system_name(self) -> str:
        return "Porphyry"


class RegiomontanusHouses(SwissHouseSystemBase):
    """Regiomontanus house system engine."""

    @property
    def system_name(self) -> str:
        return "Regiomontanus"


class CampanusHouses(SwissHouseSystemBase):
    """Campanus house system engine."""

    @property
    def system_name(self) -> str:
        return "Campanus"


class EqualMCHouses(SwissHouseSystemBase):
    """Equal (MC) house system engine."""

    @property
    def system_name(self) -> str:
        return "Equal (MC)"


class VehlowEqualHouses(SwissHouseSystemBase):
    """Vehlow Equal house system engine."""

    @property
    def system_name(self) -> str:
        return "Vehlow Equal"


class AlcabitiusHouses(SwissHouseSystemBase):
    """Alcabitius house system engine."""

    @property
    def system_name(self) -> str:
        return "Alcabitius"


class TopocentricHouses(SwissHouseSystemBase):
    """Topocentric house system engine."""

    @property
    def system_name(self) -> str:
        return "Topocentric"


class MorinusHouses(SwissHouseSystemBase):
    """Morinus house system engine."""

    @property
    def system_name(self) -> str:
        return "Morinus"


class EqualVertexHouses(SwissHouseSystemBase):
    """Equal (Vertex) house system engine."""

    @property
    def system_name(self) -> str:
        return "Equal (Vertex)"


class GauquelinHouses(SwissHouseSystemBase):
    """Gauquelin house system engine."""

    @property
    def system_name(self) -> str:
        return "Gauquelin"


class HorizontalHouses(SwissHouseSystemBase):
    """Horizontal house system engine."""

    @property
    def system_name(self) -> str:
        return "Horizontal"


class KrusinskiHouses(SwissHouseSystemBase):
    """Krusinski house system engine."""

    @property
    def system_name(self) -> str:
        return "Krusinski"


class AxialRotationHouses(SwissHouseSystemBase):
    """Axial Rotation house system engine."""

    @property
    def system_name(self) -> str:
        return "Axial Rotation"


class APCHouses(SwissHouseSystemBase):
    """APC house system engine."""

    @property
    def system_name(self) -> str:
        return "APC"
