from django.db.models import Model


class EnvironmentField:
    """
    Descriptor for defining environment fields.

    Example:
        semester = EnvironmentField(
            model=Semester,
            session_key='semester',
            label='Semester',
            queryset=Semester.objects.filter(is_active=True)
        )
    """

    def __init__(self, model, session_key, label=None, queryset=None, required=False):
        self.model = model
        self.session_key = session_key
        self.label = label
        self.queryset = queryset
        self.required = required
        self.name = None  # Set by Environment metaclass

    def get_queryset(self):
        """Return the queryset for this field's choices."""
        if self.queryset is not None:
            return self.queryset
        return self.model.objects.all()

    def get_label(self):
        """Return the display label for this field."""
        if self.label:
            return self.label
        return self.model._meta.verbose_name.title()

    def get_value_from_session(self, session):
        """Get the current value from session."""
        env_data = session.get("environment", {})
        pk = env_data.get(self.session_key)
        if pk is not None:
            try:
                return self.model.objects.get(pk=pk)
            except self.model.DoesNotExist:
                return None
        return None

    def set_value_in_session(self, session, value):
        """Set the value in session."""
        if "environment" not in session:
            session["environment"] = {}

        if value is None:
            session["environment"].pop(self.session_key, None)
        elif isinstance(value, Model):
            session["environment"][self.session_key] = value.pk
        else:
            session["environment"][self.session_key] = value

        session.modified = True


class EnvironmentMeta(type):
    """Metaclass that collects EnvironmentField instances."""

    def __new__(mcs, name, bases, namespace):
        fields = {}

        # Collect fields from base classes
        for base in bases:
            if hasattr(base, "_fields"):
                fields.update(base._fields)

        # Collect fields from current class
        for key, value in list(namespace.items()):
            if isinstance(value, EnvironmentField):
                value.name = key
                fields[key] = value

        cls = super().__new__(mcs, name, bases, namespace)
        cls._fields = fields
        return cls


class Environment(metaclass=EnvironmentMeta):
    """
    Base class for defining environment configurations per app.

    Example:
        @EnvironmentRegistry.register('batches')
        class BatchesEnvironment(Environment):
            redirect_url = reverse_lazy('batches:list')

            semester = EnvironmentField(
                model=Semester,
                session_key='semester',
                label='Semester'
            )
    """

    _fields = {}
    redirect_url = None  # URL to redirect to when environment changes

    def __init__(self, request=None):
        self.request = request
        self.session = request.session if request else {}

    @classmethod
    def get_redirect_url(cls):
        """Return the URL to redirect to when environment changes."""
        return cls.redirect_url

    @classmethod
    def get_fields(cls):
        """Return all environment fields defined on this class."""
        return cls._fields

    def get_field_values(self):
        """Return dict of field_name -> current value from session."""
        values = {}
        for name, field in self._fields.items():
            values[name] = field.get_value_from_session(self.session)
        return values

    def get_field_data(self):
        """
        Return list of field data for rendering.
        Each item contains: name, field, value, queryset, label
        """
        data = []
        for name, field in self._fields.items():
            data.append(
                {
                    "name": name,
                    "field": field,
                    "session_key": field.session_key,
                    "value": field.get_value_from_session(self.session),
                    "queryset": field.get_queryset(),
                    "label": field.get_label(),
                    "required": field.required,
                }
            )
        return data

    def get_filter_kwargs(self):
        """
        Return kwargs suitable for queryset.filter().
        Maps field names to their session values.
        """
        kwargs = {}
        for name, field in self._fields.items():
            value = field.get_value_from_session(self.session)
            if value is not None:
                kwargs[name] = value
        return kwargs

    def get_session_keys(self):
        """Return list of session keys used by this environment."""
        return [field.session_key for field in self._fields.values()]

    def get_form_initial(self):
        """Return initial data for forms based on environment values."""
        initial = {}
        for name, field in self._fields.items():
            value = field.get_value_from_session(self.session)
            if value is not None:
                initial[name] = value
        return initial
