from typing import Optional, List
from datetime import datetime
from abc import ABC, abstractmethod
from analogai.foundation.core.value_objects.time import Time
from analogai.foundation.common.enums.deontic_modality import DeonticModality
from analogai.foundation.common.utils.time_utils import Tense
from analogai.foundation.common.utils.time_utils.relative_time_formatter import RelativeTimeFormatter
from analogai.foundation.common.utils.time_utils.time_converter import TimeConverter
from analogai.foundation.common.utils.time_utils.timezone_utils import TimezoneUtils
from analogai.foundation.common.utils.statement_serializers.helpers.deontic_formatter import DeonticFormatter
from analogai.foundation.common.utils.statement_serializers.helpers.verb_conjugator import VerbConjugator


def _has_time_data(time_obj: Optional[Time]) -> bool:
    return TimeConverter.has_time_data(time_obj)


def _determine_tense(
    time_obj: Optional[Time],
    reference_time: Optional[datetime] = None,
    timezone: Optional[str] = None,
) -> Tense:
    if not _has_time_data(time_obj):
        return Tense.PRESENT

    reference_time = TimezoneUtils.get_timezone_aware_datetime(timezone, reference_time)

    time_date = TimeConverter.to_datetime(time_obj, reference_time)
    if time_date is None:
        return Tense.PRESENT

    if time_date.tzinfo is None:
        time_date = time_date.replace(tzinfo=reference_time.tzinfo)

    has_specific_time = time_obj.hour is not None or time_obj.minute is not None

    time_date_only = time_date.date()
    reference_date_only = reference_time.date()

    if time_date_only < reference_date_only:
        return Tense.PAST
    if time_date_only > reference_date_only:
        return Tense.FUTURE

    if has_specific_time and time_date < reference_time:
        return Tense.PAST
    return Tense.PRESENT


def _format_absolute_time(time_obj: Optional[Time]) -> str:
    if not _has_time_data(time_obj):
        return ""
    return RelativeTimeFormatter._format_absolute(time_obj)


class SentenceComponent(ABC):
    @abstractmethod
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        pass
    
    @abstractmethod
    def get_component_type(self) -> str:
        pass


class BaseStatementComponent(SentenceComponent):
    def __init__(self, source: str, verb: str, target: str, tense: Tense = Tense.PRESENT, is_plural: bool = False):
        self.source = source
        self.verb = verb
        self.target = target
        self.tense = tense
        self.is_plural = is_plural
    
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        conjugated_verb = VerbConjugator.conjugate(self.verb, self.tense, self.is_plural)
        return f"{self.source} {conjugated_verb} {self.target}"
    
    def get_component_type(self) -> str:
        return "base"


class TimePrefixComponent(SentenceComponent):
    def __init__(self, time_obj: Time, tense: Tense, prefix: Optional[str] = None):
        self.time_obj = time_obj
        self.tense = tense
        self.prefix = prefix
    
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        time_str = _format_absolute_time(self.time_obj)
        if not time_str:
            return ""

        def _is_clock_time(value: str) -> bool:
            if ":" in value:
                return True
            lowered = value.lower()
            return lowered.endswith(" am") or lowered.endswith(" pm") or lowered.endswith("am") or lowered.endswith("pm")

        def _is_date_phrase(value: str) -> bool:
            lowered = value.lower()
            months = [
                "january",
                "february",
                "march",
                "april",
                "may",
                "june",
                "july",
                "august",
                "september",
                "october",
                "november",
                "december",
            ]
            if any(month in lowered for month in months):
                return True
            if "," in value and any(char.isdigit() for char in value):
                return True
            return False

        if self.prefix:
            return f"{self.prefix} {time_str}"

        if _is_clock_time(time_str):
            return f"at {time_str}"

        if _is_date_phrase(time_str):
            return f"on {time_str}"

        return f"at {time_str}"
    
    def get_component_type(self) -> str:
        return "time_prefix"


class TimeRangeComponent(SentenceComponent):
    def __init__(self, from_time: Optional[Time], to_time: Optional[Time]):
        self.from_time = from_time
        self.to_time = to_time
    
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        from_str = ""
        to_str = ""
        
        if self.from_time and _has_time_data(self.from_time):
            from_str = _format_absolute_time(self.from_time)
        
        if self.to_time and _has_time_data(self.to_time):
            to_str = _format_absolute_time(self.to_time)
        
        if from_str and to_str:
            if from_str == to_str:
                return f"in {from_str}"
            return f"from {from_str} to {to_str}"
        
        if from_str and not to_str:
            return f"from {from_str}"
        if to_str and not from_str:
            return f"to {to_str}"
        
        return ""
    
    def get_component_type(self) -> str:
        return "time_range"


class LocationComponent(SentenceComponent):
    def __init__(self, location: str):
        self.location = location
    
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        if self.location and self.location.strip():
            return f"at {self.location}"
        return ""
    
    def get_component_type(self) -> str:
        return "location"


class AttributeValueComponent(SentenceComponent):
    def __init__(self, tag: str, value, unit: Optional[str] = None):
        self.tag = tag
        self.value = value
        self.unit = unit
    
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        if self.unit:
            return f"with {self.tag} {self.value} {self.unit}"
        return f"with {self.tag} {self.value}"
    
    def get_component_type(self) -> str:
        return "attribute_value"


class DeonticWrapperComponent(SentenceComponent):
    def __init__(self, deontic_modality: DeonticModality):
        self.deontic_modality = deontic_modality
    
    def build(self, builder: 'NaturalSentenceBuilder') -> str:
        location_component = None
        for component in builder.components:
            if component.get_component_type() == "location":
                location_component = component
                break
        
        location_str = ""
        if location_component:
            location_str = location_component.build(builder)
            if location_str.startswith("at "):
                location_str = location_str[3:]
            location_str = f" in {location_str}" if location_str else ""
        
        if builder.source and builder.verb and builder.target:
            deontic_prefix = DeonticFormatter.format_deontic_prefix(
                self.deontic_modality,
                builder.tense
            )
            if deontic_prefix:
                infinitive_verb = VerbConjugator.to_infinitive(builder.verb)
                statement = f"{builder.source} {deontic_prefix} {infinitive_verb} {builder.target}{location_str}"
                return statement
        
        deontic_phrase = DeonticFormatter.format_deontic_phrase(
            self.deontic_modality,
            builder.tense
        )
        if not deontic_phrase:
            return builder.get_base_statement()
        
        if builder.source and builder.verb and builder.target:
            infinitive_verb = VerbConjugator.to_infinitive(builder.verb)
            statement = f"{deontic_phrase} {infinitive_verb} {builder.target}{location_str}"
            return statement
        
        base_statement = builder.get_base_statement()
        if location_str:
            return f"{deontic_phrase}, that {base_statement}{location_str}"
        return f"{deontic_phrase}, that {base_statement}"
    
    def get_component_type(self) -> str:
        return "deontic"


class NaturalSentenceBuilder:
    def __init__(self, reference_time: Optional[datetime] = None, timezone: Optional[str] = None):
        self.reference_time = TimezoneUtils.get_timezone_aware_datetime(timezone, reference_time)
        self.timezone = timezone
        self.components: List[SentenceComponent] = []
        self.base_statement: Optional[str] = None
        self.source: Optional[str] = None
        self.verb: Optional[str] = None
        self.target: Optional[str] = None
        self.tense: Tense = Tense.PRESENT
        self.is_plural: bool = False
    
    def with_base_statement(
        self,
        source: str,
        verb: str,
        target: str,
        is_plural: bool = False,
        quantity: Optional[float] = None,
    ) -> 'NaturalSentenceBuilder':
        from analogai.foundation.common.utils.repr_utils import pluralize_noun, format_quantity
        
        self.source = source
        self.verb = verb
        
        if quantity is not None and quantity > 0:
            formatted_quantity = format_quantity(quantity)
            pluralized_target = pluralize_noun(target, quantity)
            self.target = f"{formatted_quantity} {pluralized_target}"
        else:
            self.target = target
        
        self.is_plural = is_plural
        self.base_statement = f"{source} {verb} {self.target}"
        return self
    
    def with_location(self, location: Optional[str]) -> 'NaturalSentenceBuilder':
        if location and location.strip():
            self.components.append(LocationComponent(location))
        return self
    
    def with_attributes(self, attributes: Optional[List]) -> 'NaturalSentenceBuilder':
        if attributes:
            for m in attributes:
                self.components.append(AttributeValueComponent(m.tag, m.value, m.unit))
        return self

    def with_measures(self, measures: Optional[List]) -> 'NaturalSentenceBuilder':
        return self.with_attributes(measures)
    
    def with_from_time(self, from_time: Optional[Time], prefix: Optional[str] = None) -> 'NaturalSentenceBuilder':
        if from_time and _has_time_data(from_time):
            self.tense = _determine_tense(from_time, self.reference_time, self.timezone)
            self.components.append(TimePrefixComponent(from_time, self.tense, prefix=prefix))
        return self
    
    def with_time_range(self, from_time: Optional[Time], to_time: Optional[Time]) -> 'NaturalSentenceBuilder':
        if (from_time and _has_time_data(from_time)) or (to_time and _has_time_data(to_time)):
            primary_time = from_time if from_time else to_time
            if primary_time:
                self.tense = _determine_tense(primary_time, self.reference_time, self.timezone)
            self.components.append(TimeRangeComponent(from_time, to_time))
        return self
    
    def with_deontic_modality(
        self,
        deontic_modality: Optional[DeonticModality]
    ) -> 'NaturalSentenceBuilder':
        if deontic_modality:
            self.components.append(DeonticWrapperComponent(deontic_modality))
        return self
    
    def get_base_statement(self) -> str:
        if self.source and self.verb and self.target:
            conjugated_verb = VerbConjugator.conjugate(self.verb, self.tense, self.is_plural)
            return f"{self.source} {conjugated_verb} {self.target}"
        return self.base_statement or ""
    
    def build(self) -> str:
        if not self.base_statement:
            return ""
        
        deontic_component = None
        other_components = []
        
        for component in self.components:
            if component.get_component_type() == "deontic":
                deontic_component = component
            else:
                other_components.append(component)
        
        if deontic_component:
            return deontic_component.build(self)
        
        if self.source and self.verb and self.target:
            conjugated_verb = VerbConjugator.conjugate(self.verb, self.tense, self.is_plural)
            base_statement = f"{self.source} {conjugated_verb} {self.target}"
        else:
            base_statement = self.base_statement
        
        prefix_parts = []
        suffix_parts = []
        for component in other_components:
            part = component.build(self)
            if not part:
                continue
            component_type = component.get_component_type()
            if component_type in {"time_prefix", "time_range", "location"}:
                suffix_parts.append(part)
            else:
                prefix_parts.append(part)

        sentence = base_statement
        if prefix_parts:
            sentence = f"{', '.join(prefix_parts)}, {sentence}"
        if suffix_parts:
            sentence = f"{sentence}, {', '.join(suffix_parts)}"
        return sentence


__all__ = ["NaturalSentenceBuilder"]
