from typing import Optional
from datetime import datetime, timedelta
from analogai.foundation.core.value_objects.time import Time
from analogai.foundation.common.utils.time_utils.tense import Tense
from analogai.foundation.common.utils.time_utils.time_converter import TimeConverter
from analogai.foundation.common.utils.time_utils.relative_time_formatter import RelativeTimeFormatter
from analogai.foundation.common.utils.time_utils.timezone_utils import TimezoneUtils


class TimeFormatter:
    
    @staticmethod
    def has_time_data(time_obj: Optional[Time]) -> bool:
        return TimeConverter.has_time_data(time_obj)
    
    @staticmethod
    def determine_tense(time_obj: Optional[Time], reference_time: Optional[datetime] = None, timezone: Optional[str] = None) -> Tense:
        if not TimeFormatter.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
        elif time_date_only > reference_date_only:
            return Tense.FUTURE
        else:
            if has_specific_time and time_date < reference_time:
                return Tense.PAST
            else:
                return Tense.PRESENT
    
    @staticmethod
    def format_natural_language(time_obj: Optional[Time], reference_time: Optional[datetime] = None, timezone: Optional[str] = None) -> str:
        if not TimeFormatter.has_time_data(time_obj):
            return ""
        
        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 RelativeTimeFormatter._format_absolute(time_obj)
        
        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()
        days_diff = (time_date_only - reference_date_only).days
        
        if not has_specific_time:
            if days_diff == 0:
                return "today"
            elif days_diff == -1:
                return "yesterday"
            elif days_diff == 1:
                return "tomorrow"
            else:
                delta = timedelta(days=abs(days_diff))
                if days_diff < 0:
                    return TimeFormatter._format_past_time(delta, time_date, reference_time, time_obj)
                else:
                    return TimeFormatter._format_future_time(delta, time_date, reference_time, time_obj)
        
        if days_diff == 0 and time_obj.hour is not None and time_obj.hour == reference_time.hour and time_obj.minute is None:
            return "now"
        
        delta = time_date - reference_time
        total_seconds = delta.total_seconds()
        
        if total_seconds > 0:
            return TimeFormatter._format_future_time(delta, time_date, reference_time, time_obj)
        elif total_seconds < 0:
            return TimeFormatter._format_past_time(abs(delta), time_date, reference_time, time_obj)
        else:
            return "today"
    
    @staticmethod
    def format_for_range(time_obj: Optional[Time], reference_time: Optional[datetime] = None, timezone: Optional[str] = None) -> str:
        reference_time = TimezoneUtils.get_timezone_aware_datetime(timezone, reference_time)
        return RelativeTimeFormatter.format_for_range(time_obj, reference_time)
    
    @staticmethod
    def _format_future_time(delta: timedelta, time_date: datetime, reference: datetime, time_obj: Time) -> str:
        time_date_only = time_date.date()
        reference_date_only = reference.date()
        days_diff = (time_date_only - reference_date_only).days
        
        has_specific_time = time_obj.hour is not None or time_obj.minute is not None
        total_seconds = delta.total_seconds()
        
        if days_diff == 1:
            if has_specific_time and total_seconds < 86400:
                if total_seconds < 3600:
                    minutes = int(total_seconds / 60)
                    if minutes == 1:
                        return "in 1 minute"
                    else:
                        return f"in {minutes} minutes"
                else:
                    hours = int(total_seconds / 3600)
                    remaining_seconds = total_seconds % 3600
                    minutes = int(remaining_seconds / 60)
                    
                    if hours > 0 and minutes > 0:
                        if hours == 1 and minutes == 1:
                            return "in 1 hour and 1 minute"
                        elif hours == 1:
                            return f"in 1 hour and {minutes} minutes"
                        elif minutes == 1:
                            return f"in {hours} hours and 1 minute"
                        else:
                            return f"in {hours} hours and {minutes} minutes"
                    elif hours > 0:
                        if hours == 1:
                            return "in 1 hour"
                        else:
                            return f"in {hours} hours"
            return "tomorrow"
        
        if total_seconds < 3600:
            minutes = int(total_seconds / 60)
            if minutes == 0:
                return "in less than a minute"
            elif minutes == 1:
                return "in 1 minute"
            else:
                return f"in {minutes} minutes"
        
        elif total_seconds < 86400:
            hours = int(total_seconds / 3600)
            remaining_seconds = total_seconds % 3600
            minutes = int(remaining_seconds / 60)
            
            if hours > 0 and minutes > 0:
                if hours == 1 and minutes == 1:
                    return "in 1 hour and 1 minute"
                elif hours == 1:
                    return f"in 1 hour and {minutes} minutes"
                elif minutes == 1:
                    return f"in {hours} hours and 1 minute"
                else:
                    return f"in {hours} hours and {minutes} minutes"
            elif hours > 0:
                if hours == 1:
                    return "in 1 hour"
                else:
                    return f"in {hours} hours"
            elif minutes > 0:
                if minutes == 1:
                    return "in 1 minute"
                else:
                    return f"in {minutes} minutes"
        elif days_diff < 7:
            return f"in {days_diff} days"
        elif days_diff < 14:
            return "next week"
        elif days_diff < 30:
            weeks = days_diff // 7
            return f"in {weeks} weeks"
        elif days_diff < 60:
            return "next month"
        elif days_diff < 365:
            months = days_diff // 30
            if months == 1:
                return "next month"
            return f"in {months} months"
        else:
            years = days_diff // 365
            if years == 1:
                return "next year"
            else:
                return f"in {years} years"
    
    @staticmethod
    def _format_past_time(delta: timedelta, time_date: datetime, reference: datetime, time_obj: Time) -> str:
        total_seconds = delta.total_seconds()
        
        if total_seconds < 3600:
            minutes = int(total_seconds / 60)
            if minutes == 0:
                return "just now"
            elif minutes == 1:
                return "1 minute ago"
            else:
                return f"{minutes} minutes ago"
        
        hours = int(total_seconds / 3600)
        
        time_date_only = time_date.date()
        reference_date_only = reference.date()
        
        if time_date_only == reference_date_only:
            if hours <= 6:
                if hours == 1:
                    return "1 hour ago"
                return f"{hours} hours ago"
            return "today"
        
        days_diff = (reference_date_only - time_date_only).days
        
        if days_diff == 1:
            return "yesterday"
        elif days_diff < 7:
            return f"{days_diff} days ago"
        elif days_diff < 14:
            return "last week"
        elif days_diff < 30:
            weeks = days_diff // 7
            return f"{weeks} weeks ago"
        elif days_diff < 60:
            return "last month"
        elif days_diff < 365:
            months = days_diff // 30
            if months == 1:
                return "last month"
            return f"{months} months ago"
        else:
            years = days_diff // 365
            if years == 1:
                return "last year"
            if years > 10:
                return str(time_date.year)
            return f"{years} years ago"
    
    @staticmethod
    def is_relative_date(time_obj: Optional[Time], reference_time: Optional[datetime] = None) -> bool:
        if not TimeFormatter.has_time_data(time_obj):
            return False
        
        if reference_time is None:
            reference_time = datetime.now()
        
        time_date = TimeConverter.to_datetime(time_obj, reference_time)
        if time_date is None:
            return False
        
        time_date_only = time_date.date()
        reference_date_only = reference_time.date()
        
        tomorrow = reference_date_only + timedelta(days=1)
        yesterday = reference_date_only - timedelta(days=1)
        
        return time_date_only == reference_date_only or time_date_only == tomorrow or time_date_only == yesterday
