"""
Absfuyu: Fun
------------
Numerology

Version: 6.8.0
Date updated: 26/04/2026 (dd/mm/yyyy)
"""

# Module level
# ---------------------------------------------------------------------------
__all__ = ["NumerologyCalculator", "NumberologyResult"]


# Library
# ---------------------------------------------------------------------------
import calendar
from dataclasses import dataclass
from datetime import date, datetime
from typing import Self

from absfuyu.core.baseclass import BaseClass, BaseDataclass
from absfuyu.core.dummy_func import unidecode


# Class
# ---------------------------------------------------------------------------
@dataclass
class NumberologyResult(BaseDataclass):
    name: str
    birthdate: datetime | date
    life_path: int
    expression: int
    soul_urge: int
    personality: int
    personal_year: int
    lucky_days: list[int]

    def to_report(self):
        """
        Full numerology report.
        """
        MEANINGS: dict[int, str] = {
            1: "Leader, independence, ambition.",
            2: "Harmony, partnership, diplomacy.",
            3: "Creativity, joy, expression.",
            4: "Discipline, structure, hard work.",
            5: "Freedom, change, adventure.",
            6: "Responsibility, family, care.",
            7: "Wisdom, analysis, spirituality.",
            8: "Power, business, success.",
            9: "Compassion, humanity, completion.",
            11: "Intuition, spiritual messenger.",
            22: "Master builder, legacy.",
            33: "Master teacher, service.",
        }
        return f"""
==============================
NUMEROLOGY REPORT
==============================
Name          : {self.name}
Birthdate     : {self.birthdate.strftime("%Y/%m/%d")}

Long-term life direction
Life Path     : {self.life_path}
Meaning       : {MEANINGS[self.life_path]}

Personality + talents
Expression    : {self.expression}
Meaning       : {MEANINGS[self.expression]}

Inner emotional needs
Soul Urge     : {self.soul_urge}
Meaning       : {MEANINGS[self.soul_urge]}

Personality   : {self.personality}
Meaning       : {MEANINGS[self.personality]}

Current Year  : {self.personal_year}
Meaning       : {MEANINGS[self.personal_year]}

Lucky Days    : {self.lucky_days}
==============================
"""


class NumerologyCalculator(BaseClass):
    """
    A utility class for calculating common numerology numbers.

    Supported calculations
    ----------------------
    - Life Path Number
    - Expression / Destiny Number
    - Soul Urge Number
    - Personality Number

    Notes
    -----
    This implementation follows a common Pythagorean numerology system.

    Letter mapping:

    ========== =====
    Letters    Value
    ========== =====
    A J S      1
    B K T      2
    C L U      3
    D M V      4
    E N W      5
    F O X      6
    G P Y      7
    H Q Z      8
    I R        9
    ========== =====

    Master numbers preserved during reduction:
    - 11
    - 22
    - 33
    """

    LETTER_MAP: dict[str, int] = {
        **dict.fromkeys(list("AJS"), 1),
        **dict.fromkeys(list("BKT"), 2),
        **dict.fromkeys(list("CLU"), 3),
        **dict.fromkeys(list("DMV"), 4),
        **dict.fromkeys(list("ENW"), 5),
        **dict.fromkeys(list("FOX"), 6),
        **dict.fromkeys(list("GPY"), 7),
        **dict.fromkeys(list("HQZ"), 8),
        **dict.fromkeys(list("IR"), 9),
    }

    VOWELS: set[str] = set("AEIOU")

    MASTER_NUMBERS: set[int] = {11, 22, 33}

    def __init__(
        self,
        name: str,
        birthdate: str,
        *,
        date_str_format: str = "%Y/%m/%d",
        unidecode_name: bool = True,
    ) -> None:
        """
        Numberology calculator instance

        Parameters
        ----------
        name : str
            Name

        birthdate : str
            Date of birth

        date_str_format : str, optional
            Date of birth in which format, by default ``"%Y/%m/%d"``

        unidecode_name : bool
            Unidecode the name, by default ``True``
        """
        if unidecode_name:
            name = unidecode(name)
        self.name = name.strip()

        self.birthdate = birthdate
        self.date_str_format = date_str_format
        self.date_obj = datetime.strptime(birthdate, self.date_str_format)

    # Support
    @classmethod
    def from_datetime(cls, name: str, birthdate: datetime | date) -> Self:
        return cls(name, birthdate.strftime("%Y/%m/%d"))

    @classmethod
    def reduce_number(cls, n: int) -> int:
        """
        Reduce a number to a single digit or master number.

        The digits are repeatedly summed until the result becomes:
        - one digit (1 to 9), or
        - a master number (11, 22, 33)

        Parameters
        ----------
        n : int
            Input number to reduce.

        Returns
        -------
        int
            Reduced numerology value.

        Examples
        --------
        >>> NumerologyCalculator.reduce_number(28)
        1

        Explanation:
        2 + 8 = 10
        1 + 0 = 1
        """
        while n > 9 and n not in cls.MASTER_NUMBERS:
            n = sum(int(x) for x in str(n))
        return n

    @classmethod
    def _text_to_numbers(cls, text: str) -> list[int]:
        """
        Convert letters in a name to numerology values.

        Non-alphabetic characters are ignored.

        Parameters
        ----------
        text : str
            Input text.

        Returns
        -------
        list[int]
            List of converted numbers.

        Examples
        --------
        >>> NumerologyCalculator._text_to_numbers("ABC")
        [1, 2, 3]
        """
        text = text.upper()
        return [cls.LETTER_MAP[ch] for ch in text if ch.isalpha() and ch in cls.LETTER_MAP]

    # Calculation
    def cal_life_path(self) -> int:
        """
        Calculate Life Path Number from birth date. (Long-term life direction)

        Eg: Birthdate is 15/08/1995

        Process:

        1 + 5 + 0 + 8 + 1 + 9 + 9 + 5 = 38

        3 + 8 = 11

        Master number -> 11
        """
        total = sum(int(x) for x in self.date_obj.strftime("%Y%m%d"))
        return self.reduce_number(total)

    def cal_expression_number(self) -> int:
        """
        Calculate Expression Number from name. (Personality + talents)
        """
        values = sum(self._text_to_numbers(self.name))
        return self.reduce_number(values)

    def cal_soul_urge(self) -> int:
        """
        Calculate Soul Urge Number from name (vowels). (Inner emotional needs)
        """
        values = [self.LETTER_MAP[ch] for ch in self.name.upper() if ch in self.VOWELS]
        return self.reduce_number(sum(values))

    def cal_personality_number(self) -> int:
        """
        Calculate Personality Number from name (no vowels).
        """
        values = [
            self.LETTER_MAP[ch]
            for ch in self.name.upper()
            if ch.isalpha() and ch not in self.VOWELS and ch in self.LETTER_MAP
        ]
        return self.reduce_number(sum(values))

    def cal_personal_year(self, year: int | None = None) -> int:
        """
        Calculate personal year cycle.

        Formula:
        birth day + birth month + current year digits
        """
        if year is None:
            year = datetime.now().year

        total = sum(int(x) for x in f"{self.date_obj.day}{self.date_obj.month}{year}")

        return self.reduce_number(total)

    def cal_lucky_days(self, month: int | None = None) -> list[int]:
        """
        Return lucky days in current month.

        Lucky day = day number matching Life Path.
        """
        now = datetime.now()

        if month is None:
            month = now.month

        days_in_month = calendar.monthrange(now.year, month)[1]

        lp = self.cal_life_path()

        return [day for day in range(1, days_in_month + 1) if self.reduce_number(day) == lp]

    def calulate(self) -> NumberologyResult:
        result = NumberologyResult(
            name=self.name,
            birthdate=self.date_obj,
            life_path=self.cal_life_path(),
            expression=self.cal_expression_number(),
            soul_urge=self.cal_soul_urge(),
            personality=self.cal_personality_number(),
            personal_year=self.cal_personal_year(),
            lucky_days=self.cal_lucky_days(12),
        )
        return result
