import uuid

from django.conf import settings
from django.contrib.admin import FieldListFilter, SimpleListFilter
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.urlresolvers import reverse
from django.core.cache import cache
from django.db import models
from django.db.models import Q
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from django.utils.encoding import smart_unicode, smart_text, force_text
from django.contrib.admin.options import IncorrectLookupParameters

class NullListFilter(FieldListFilter):
    
    def __init__(self, field, request, params, model, model_admin, field_path):
        self.lookup_kwarg = '%s__isnull' % field_path
        self.lookup_val = None
        try:
            self.lookup_val = request.GET.get(self.lookup_kwarg, None)
            if self.lookup_val is not None:
                if self.lookup_val in (True, 'True', 1, '1'):
                    self.lookup_val = True
                else:
                    self.lookup_val = False
        except:
            pass
        super(NullListFilter, self).__init__(field,
            request, params, model, model_admin, field_path)

    def expected_parameters(self):
        return [self.lookup_kwarg,]

    def choices(self, cl):
        for lookup, title in (
                (None, _('All')),
                (False, _('Has value')),
                (True, _('Omitted'))):
            d = {
                'selected': self.lookup_val == lookup,
                'query_string': cl.get_query_string({
                    self.lookup_kwarg: lookup,
                }, [self.lookup_kwarg]),
                'display': title,
            }
            yield d

class NotInListFilter(FieldListFilter):
    """
    Allows the use of exclude(field=value) via the URL.
    The inverse of Django's default "__in=" URL syntax.
    """
    
    def __init__(self, field, request, params, model, model_admin, field_path):
        self.field_path = field_path
        self.lookup_kwarg = '%s__notin' % field_path
        self.lookup_vals = None
        try:
            self.lookup_vals = request.GET.get(self.lookup_kwarg, None)
            if self.lookup_vals is not None:
                self.lookup_vals = self.lookup_vals.split(',')
        except:
            pass
        
        self.lookup_choices = field.get_choices(include_blank=False)
        super(NotInListFilter, self).__init__(field,
            request, params, model, model_admin, field_path)
        
        self.title = getattr(field, 'verbose_name', field_path) + ' is not'

    def expected_parameters(self):
        return [self.lookup_kwarg]
    
    def queryset(self, request, queryset):
        try:
            # Convert the __notin to a Django ORM .exclude(...)
            if self.lookup_kwarg in self.used_parameters and self.lookup_vals:
                queryset = queryset.exclude(**{self.field_path+'__in': self.lookup_vals})
            return queryset
        except ValidationError as e:
            raise IncorrectLookupParameters(e)

    def choices(self, cl):
        from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
        yield {
            'selected': self.lookup_vals is None,# and not self.lookup_val_isnull,
            'query_string': cl.get_query_string({},
                [self.lookup_kwarg]),
            'display': _('None'),
        }
        for pk_val, val in self.lookup_choices:
            yield {
                'selected': (smart_text(pk_val) in self.lookup_vals) if self.lookup_vals else False,
                'query_string': cl.get_query_string({
                    self.lookup_kwarg: pk_val,
                }, []),
                'display': val,
            }
#        if (isinstance(self.field, models.related.RelatedObject)
#                and self.field.field.null or hasattr(self.field, 'rel')
#                    and self.field.null):
#            yield {
#                'selected': bool(self.lookup_val_isnull),
#                'query_string': cl.get_query_string({
#                    self.lookup_kwarg_isnull: 'True',
#                }, [self.lookup_kwarg]),
#                'display': EMPTY_CHANGELIST_VALUE,
#            }

class CachedFieldFilter(FieldListFilter):
    """
    Caches the choices query from the model, ignoring any other filtering
    on the model.
    """
    
    cache_seconds = 3600 # 1-hour
    
    def __init__(self, field, request, params, model, model_admin, field_path):
        self.field_name = field.name
        self.lookup_kwarg = '%s__exact' % field_path
        self.lookup_kwarg2 = '%s__isnull' % field_path
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
        self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
        
        super(CachedFieldFilter, self).__init__(field, request, params, model, model_admin, field_path)
        
        self.model = model
    
    def expected_parameters(self):
        return [self.lookup_kwarg, self.lookup_kwarg2]
        
    def choices(self, cl):
        # Query cached choices.
        # Note, this purposefully gets a distinct set from the global
        # set of values, so that when we cache it, it's valid for all
        # admin queries. Yes, it may include some values that will return
        # no results on some pages, but that's an acceptable trade-off for
        # being able to shave off a lot of query time.
        cache_key = 'cff_%s_%s' \
            % (str(self.model).split("'")[1], self.field_name)
        values = cache.get(cache_key)
        if values is None:
            values = self.model.objects.all()\
                .values_list(self.field_name, flat=True)\
                .distinct().order_by(self.field_name)
            cache.set(cache_key, values, self.cache_seconds)
        
        yield {
            'selected': self.lookup_val is None and self.lookup_val2 is None,
            'query_string': cl.get_query_string({
                    self.lookup_kwarg2: '',
                }, [self.lookup_kwarg]),
            'display': _('All'),
        }
        
        for value in values:
            if value is None:
                yield {
                    'selected': self.lookup_val2,
                    'query_string': cl.get_query_string({
                            self.lookup_kwarg: value,
                        }, [self.lookup_kwarg2]),
                    'display': value,
                }
            else:
                yield {
                    'selected': self.lookup_val == value,
                    'query_string': cl.get_query_string({
                            self.lookup_kwarg: value,
                        }, [self.lookup_kwarg2]),
                    'display': value,
                }

class AjaxFieldFilter(FieldListFilter):
    """
    Allows searching for one or more field values via ajax
    and searching for them via __exact or __in.
    """
    
    template = 'admin_steroids/ajax_filter.html'
    
    #TODO:specify one-only or multiple
    def __init__(self, field, request, params, model, model_admin, field_path):
        self.field_name = field.name
        self.field = field
        
        self.lookup_kwarg = '%s__in' % field_path
        self.lookup_val = [_ for _ in request.GET.get(self.lookup_kwarg, '').split(',') if _.strip()]
        
        super(AjaxFieldFilter, self).__init__(field, request, params, model, model_admin, field_path)
        
        self.model = model
        
        self.uuid = '_'+str(uuid.uuid4())
        
#        _d = request.GET.copy()
#        del _d[self.lookup_kwarg]
#        self.base_url = request.path + '?' + _d.urlencode
#        if _d:
#            self.base_url += '&'
#        self.base_url += self.lookup_kwarg + '='
        
        self.ajax_url = reverse(
            'model_field_search',
            args=(model._meta.app_label, model.__name__.lower(), self.field_name))
    
#    def __call__(self, *args, **kwargs):
#        return
    
    def expected_parameters(self):
        return [self.lookup_kwarg]
    
    @property
    def values(self):
        return self.lookup_val
    
    def choices(self, cl):
        # Note, all these choices are for *deselecting* the value.
        # Additions will be handled dynamically via AJAX.
        yield {
            'selected': not self.lookup_val,
            'query_string': cl.get_query_string(
                new_params={},
                remove=[self.lookup_kwarg],
            ),
            'display': _('All'),
            'remove_icon': False,
            'alt': 'Remove All',
        }
        for value in self.values:
            lst_without = list(self.lookup_val)
            lst_without.remove(value)
            
            if lst_without:
                url = cl.get_query_string(
                    new_params={self.lookup_kwarg: ','.join(lst_without)},
                    remove=[self.lookup_kwarg],
                )
            else:
                url = cl.get_query_string(
                    new_params={},
                    remove=[self.lookup_kwarg],
                )
            
            # Lookup the "pretty" display value for IDs of related models.
            if isinstance(self.field, (models.ForeignKey, models.ManyToManyField, models.OneToOneField)):
                rel_model = self.field.rel.to
                #TODO:handle non-numeric IDs?
                value = str(rel_model.objects.get(id=int(value)))
            
            yield {
                'selected': True,
                'query_string': url,
                'display': value,
                'remove_icon': True,
                'alt': 'Remove',
            }

class LogEntryAdminUserFilter(SimpleListFilter):
    title = _('user')
    parameter_name = 'user'

    def lookups(self, request, model_admin):
        from django.contrib.auth.models import User
        from django.db.models import Q
        qs = User.objects.filter(Q(is_staff=True) | Q(is_superuser=True))
        qs = qs.order_by('username')
        return [(user.pk, _(str(user))) for user in qs]

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(user__pk=self.value())

        return queryset
