from __future__ import annotations

from datetime import datetime, timedelta
from django.utils import timezone as dj_tz

from django.core.cache import cache
from django.core.mail import send_mail
from django.core.management.base import BaseCommand
from django.conf import settings

from bazi.models import Person as BaziPerson
from django.contrib.auth import get_user_model


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 _timeout_seconds(num_records: int) -> int:
    blocks = max(1, (num_records + 99) // 100)
    return blocks * 10


class Command(BaseCommand):
    help = "Scan BaZi group relations processing states and mark timeouts to error; optionally re-trigger after 5 minutes."

    def handle(self, *args, **options):
        # Iterate all owners by distinct created_by in BaziPerson
        owner_ids = (
            BaziPerson.objects.exclude(created_by_id=None)
            .values_list('created_by_id', flat=True).distinct()
        )
        now = dj_tz.now()
        tech_email = getattr(settings, 'TECH_CONTACT', None) or getattr(settings, 'CONTACT_EMAIL', None)

        for uid in owner_ids:
            User = get_user_model()
            try:
                usr = User.objects.get(id=uid)
                state = usr.group_relations_state or 'idle'
                started_at = usr.group_relations_started_at.isoformat() if usr.group_relations_started_at else None
                error_at = usr.group_relations_error_at.isoformat() if usr.group_relations_error_at else None
            except User.DoesNotExist:
                continue
            num_records = BaziPerson.objects.filter(created_by_id=uid).count()
            threshold = _timeout_seconds(num_records)

            # Convert iso to dt
            def parse_iso(s: str):
                try:
                    return datetime.fromisoformat(s)
                except Exception:
                    return None

            if state == 'processing' and started_at:
                dt = parse_iso(started_at) or (now - timedelta(seconds=threshold + 1))
                if (now - dt).total_seconds() > threshold:
                    usr.group_relations_state = 'error'
                    usr.group_relations_error_at = now
                    usr.save(update_fields=['group_relations_state', 'group_relations_error_at'])
                    if tech_email:
                        try:
                            send_mail(
                                subject='BaZi group relations timeout',
                                message=f'User {uid} processing exceeded timeout ({threshold}s).',
                                from_email=None,
                                recipient_list=[tech_email],
                                fail_silently=True,
                            )
                        except Exception:
                            pass

            elif state == 'error' and error_at:
                dt = parse_iso(error_at)
                if dt and (now - dt).total_seconds() > 300:
                    usr.group_relations_state = 'processing'
                    usr.group_relations_started_at = now
                    usr.save(update_fields=['group_relations_state', 'group_relations_started_at'])

        self.stdout.write(self.style.SUCCESS('Scan completed.'))


