from __future__ import annotations

from django.db.models.signals import post_save, post_delete, pre_save
from django.dispatch import receiver
from django.db import transaction
from django.core.cache import cache
from django.contrib.auth import get_user_model
from django.utils import timezone as dj_tz

from .models import Person


def _cache_keys(user_id: int):
    prefix = f"bazi:grp:{user_id}:"
    return {
        "state": prefix + "state",
        "started_at": prefix + "started_at",
        "updated_at": prefix + "updated_at",
        "error_at": prefix + "error_at",
        "last_retry_at": prefix + "last_retry_at",
        "results": prefix + "results",
        "count": prefix + "count",
    }


def _set_group_state(user_id: int, state: str):
    # Update DB-backed state
    User = get_user_model()
    try:
        usr = User.objects.get(id=user_id)
        usr.group_relations_state = state
        if state == 'processing':
            usr.group_relations_started_at = dj_tz.now()
        usr.save(update_fields=['group_relations_state', 'group_relations_started_at'])
    except User.DoesNotExist:
        pass


def _spawn_recalc_task(user_id: int):
    """Spawn background recalculation task"""
    import subprocess
    import sys
    from django.conf import settings
    
    # Check if we're in a non-production environment
    def _is_non_production_environment():
        import sys
        import os
        from django.conf import settings
        
        # Check for test environment
        if any('test' in arg.lower() for arg in sys.argv):
            return True
            
        # Check for UAT environment
        if hasattr(settings, 'UAT_ENVIRONMENT') and settings.UAT_ENVIRONMENT:
            return True
            
        # Check for development environment
        if getattr(settings, 'DEBUG', False):
            return True
            
        # Check for specific environment variables
        if os.environ.get('DJANGO_ENV') in ['development', 'uat', 'staging']:
            return True
            
        # Check for UAT-specific hostnames
        if hasattr(settings, 'ALLOWED_HOSTS'):
            uat_hosts = ['uat', 'staging', 'test']
            if any(any(host in allowed_host.lower() for host in uat_hosts) 
                   for allowed_host in settings.ALLOWED_HOSTS):
                return True
            
        # Check for UAT-specific database names
        if hasattr(settings, 'DATABASES') and 'default' in settings.DATABASES:
            db_name = settings.DATABASES['default'].get('NAME', '')
            if any(host in db_name.lower() for host in ['uat', 'staging', 'test']):
                return True
            
        return False
    
    # In non-production environments, run synchronously to avoid threading issues
    if _is_non_production_environment():
        try:
            from django.core.management import call_command
            from django.core.management.base import CommandError
            
            # Run synchronously in non-production environments
            call_command('recalc_bazi_relations', user=user_id, verbosity=1)
        except Exception as e:
            # Log the error but don't fail the main operation
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Failed to run BaZi relations calculation synchronously for user {user_id}: {str(e)}")
        return
    
    try:
        # Use Django's built-in management command execution to ensure proper context
        try:
            from django.core.management import call_command
            from django.core.management.base import CommandError
            import threading
            
            def run_management_command():
                """Run the management command in a separate thread with proper Django context"""
                try:
                    # Mark this thread as a background recalc thread
                    import threading
                    current_thread = threading.current_thread()
                    current_thread._bazi_recalc_thread = True
                    
                    # Set the user state to processing
                    from django.contrib.auth import get_user_model
                    User = get_user_model()
                    try:
                        usr = User.objects.get(id=user_id)
                        usr.group_relations_state = 'processing'
                        usr.group_relations_started_at = dj_tz.now()
                        usr.save(update_fields=['group_relations_state', 'group_relations_started_at'])
                    except User.DoesNotExist:
                        return
                    
                    # Execute the management command directly
                    call_command('recalc_bazi_relations', user=user_id, verbosity=1)
                    
                    # Update user state to completed
                    try:
                        usr.refresh_from_db()
                        usr.group_relations_state = 'completed'
                        usr.group_relations_updated_at = dj_tz.now()
                        usr.save(update_fields=['group_relations_state', 'group_relations_updated_at'])
                    except User.DoesNotExist:
                        pass
                        
                except CommandError as e:
                    # Log the command error for debugging
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.error(f"CommandError in background BaZi relations calculation for user {user_id}: {str(e)}")
                    
                    # Update user state to error
                    try:
                        usr.refresh_from_db()
                        usr.group_relations_state = 'error'
                        usr.group_relations_error_at = dj_tz.now()
                        usr.save(update_fields=['group_relations_state', 'group_relations_error_at'])
                    except User.DoesNotExist:
                        pass
                except Exception as e:
                    # Log the general error for debugging
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.error(f"Unexpected error in background BaZi relations calculation for user {user_id}: {str(e)}", exc_info=True)
                    
                    # Update user state to error
                    try:
                        usr.refresh_from_db()
                        usr.group_relations_state = 'error'
                        usr.group_relations_error_at = dj_tz.now()
                        usr.save(update_fields=['group_relations_state', 'group_relations_error_at'])
                    except User.DoesNotExist:
                        pass
            
            # Run the management command in a background thread
            thread = threading.Thread(target=run_management_command, daemon=True)
            thread.start()
            
        except Exception as e:
            # If spawn fails, just continue - the API will handle it
            pass
    except Exception:
        # If spawn fails, just continue - the API will handle it
        pass


def _day_pillar(person: Person):
    try:
        if person and person.bazi_result and person.bazi_result.get("day"):
            d = person.bazi_result["day"]
            return d.get("god"), d.get("earth")
    except Exception:
        pass
    return None, None


@receiver(pre_save, sender=Person)
def pre_save_person(sender, instance: Person, **kwargs):
    # Stash previous day pillar for change detection
    if instance.pk:
        try:
            prev = Person.objects.get(pk=instance.pk)
            instance._prev_day_pillar = _day_pillar(prev)
            instance._prev_birth_date = prev.birth_date
            instance._prev_birth_time = prev.birth_time
        except Person.DoesNotExist:
            instance._prev_day_pillar = (None, None)
    else:
        instance._prev_day_pillar = (None, None)


@receiver(post_save, sender=Person)
def post_save_person(sender, instance: Person, created: bool, **kwargs):
    user_id = instance.created_by_id
    if not user_id:
        return

    # For API updates, delay the relation calculation to avoid transaction conflicts
    if getattr(instance, '_skip_relation_calculation', False):
        # Schedule a delayed calculation instead of skipping entirely
        def delayed_calculation():
            try:
                # For API updates, always trigger recalculation to be safe
                # since day pillar might have changed and we can't reliably track the previous value
                _set_group_state(user_id, "processing")
                _spawn_recalc_task(user_id)
            except Exception:
                pass  # Silently fail to avoid breaking API requests
                
        # Schedule to run after current transaction commits with a small delay
        import threading
        def run_delayed():
            import time
            import threading
            # Mark this thread as a background recalc thread
            current_thread = threading.current_thread()
            current_thread._bazi_recalc_thread = True
            time.sleep(0.5)  # Small delay to ensure API transaction is fully committed
            delayed_calculation()
        thread = threading.Thread(target=run_delayed)
        thread.daemon = True
        thread.start()
        return

    # When a new record is added, trigger recalculation
    if created:
        transaction.on_commit(lambda: _set_group_state(user_id, "processing"))
        transaction.on_commit(lambda: _spawn_recalc_task(user_id))
        return

    # For updates: only if day pillar changed
    prev_g, prev_e = getattr(instance, "_prev_day_pillar", (None, None))
    cur_g, cur_e = _day_pillar(instance)
    if (prev_g, prev_e) != (cur_g, cur_e):
        # If owner changed day pillar, this invalidates all pairwise & groups
        transaction.on_commit(lambda: _set_group_state(user_id, "processing"))
        transaction.on_commit(lambda: _spawn_recalc_task(user_id))


@receiver(post_delete, sender=Person)
def post_delete_person(sender, instance: Person, **kwargs):
    user_id = instance.created_by_id
    if not user_id:
        return
    # Any deletion requires full group recompute
    transaction.on_commit(lambda: _set_group_state(user_id, "processing"))
    transaction.on_commit(lambda: _spawn_recalc_task(user_id))


