from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import smart_text
#from django import forms
from passerelle.base.models import BaseResource
from passerelle.utils.jsonresponse import APIError
from passerelle.utils.api import endpoint

import sys
import jsonfield

from .utils import base


#TODO
# derive CsvDataSource connector
# default connexion values for CreateView
# read py files in passerelle/base/

CHOICES = (('1', 'BASE',), ('2', 'LEVEL',), ('3', 'SUBTREE',))
OPERATIONS = (('1', 'Search',), ('2', 'Add',), ('3', 'Modify',), ('4', 'Delete',), ('5', 'LDIF',))

# Create your models here.
def format_person(p):
    data = {} #TODO format to LDAP InetOrgPerson schema?
    return data

def format_org_unit(u):
    data = {}
    return data

def get_org_unit(u):
    return 0

class LDAPResource(BaseResource):
    # XXX normalize max_length values
    #ldif_file = models.FileField('LDIF File', upload_to='ldif', blank=True) # For future uses
    server = models.CharField('Server name', max_length=200, default='localhost')
    user = models.CharField('Username (full DN)', max_length=200, blank=True)
    password = models.CharField('Password', max_length=50, blank=True)

    #columns_keynames = models.CharField(
    #    max_length=256,
    #    verbose_name=_('Column keynames'),
    #    default='id, text',
    #    help_text=_('ex: id,text,data1,data2'), blank=True)
    #skip_header = models.BooleanField(_('Skip first line'), default=False)
    _dialect_options = jsonfield.JSONField(editable=False, null=True) #FIXME
    #sheet_name = models.CharField(_('Sheet name'), blank=True, max_length=150)

    category = 'Identity Management Connectors'
    class Meta:
        verbose_name = 'LDAP'

    def get(self, request, *args, **kwargs):
        return HttpResponse("Dummy response view")

    def get_absolute_url(self):
        #return reverse('ldap-view', kwargs={'slug': self.slug})
        return u"/ldap/"+self.slug #FIXME

    @classmethod
    def get_icon_class(cls):
        return 'grid'

    @classmethod
    def get_add_url(cls):
        #return reverse('ldap-add')
        return '/manage/ldap/add'

    @classmethod
    def get_verbose_name(cls):
        #return cls._meta.verbose_name
        return "LDAP Connector"

    @classmethod
    def is_enabled(cls):
        return True

    def clean(self, *args, **kwargs):
        #file_type = self.ldif_file.name.split('.')[-1]
        #if file_type in ('ods', 'xls', 'xlsx') and not self.sheet_name:
        #    raise ValidationError(_('You must specify a sheet name'))
        return super(LDAPResource, self).clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        #file_type = self.ldif_file.name.split('.')[-1]
        #if file_type not in ('ods', 'xls', 'xlsx'):
        #    content = self.get_content_without_bom()
        #    dialect = csv.Sniffer().sniff(content)
        #    self.dialect_options = {
        #        k: v for k, v in vars(dialect).items() if not k.startswith('_')
        #    }
        return super(LDAPResource, self).save(*args, **kwargs)

    @property
    def dialect_options(self): #FIXME
        """turn dict items into string
        """
        # Set dialect_options if None
        if self._dialect_options is None:
            self.save()

        options = {}
        for k, v in self._dialect_options.items():
            if isinstance(v, unicode):
                v = v.encode('ascii')
            options[k.encode('ascii')] = v

        return options

    @dialect_options.setter
    def dialect_options(self, value):
        self._dialect_options = value

    def get_content_without_bom(self): # Useless here ?
        self.ldif_file.seek(0)
        content = self.ldif_file.read()
        return content.decode('utf-8-sig', 'ignore').encode('utf-8')

    def get_data(self, filters=None):
        #titles = [t.strip() for t in self.columns_keynames.split(',')] #FIXME
        #indexes = [titles.index(t) for t in titles if t] #FIXME
        #caption = [titles[i] for i in indexes] #FIXME

        ## validate filters (appropriate columns must exist)
        #if filters:
        #    for filter_key in filters.keys():
        #        if not filter_key.split(lookups.DELIMITER)[0] in titles:
        #            del filters[filter_key]

        #rows = self.get_rows()

        #data = []

        ## build a generator of all filters
        #def filters_generator(filters, titles): #FIXME
        #    if not filters:
        #        return
        #    for key, value in filters.items():
        #        try:
        #            key, op = key.split(lookups.DELIMITER)
        #        except (ValueError,):
        #            op = 'eq'

        #        index = titles.index(key)

        #        yield lookups.get_lookup(op, index, value)

        ## apply filters to data #FIXME
        #def super_filter(filters, data):
        #    for f in filters:
        #        data = itertools.ifilter(f, data)
        #    return data

        #matches = super_filter(
        #    filters_generator(filters, titles), rows
        #)

        #for row in matches:
        #    line = []
        #    for i in indexes:
        #        try:
        #            line.append(row[i])
        #        except IndexError:
        #            line.append('')

        #    data.append(dict(zip(caption, line)))

        #return data
        return 0

    #@property
    #def titles(self): #FIXME
    #    return [smart_text(t.strip()) for t in self.columns_keynames.split(',')]

    #@endpoint('json-api', perm='can_access', methods=['get'],
    #          name='query', pattern='^(?P<query_name>[\w-]+)/$')
    def select(self, request, query_name, **kwargs):
        try:
            query = Query.objects.get(resource=self.id, slug=query_name)
        except Query.DoesNotExist:
            raise APIError(u'no such query')

    #    titles = self.titles
    #    rows = self.get_rows()
    #    data = [dict(zip(titles, line)) for line in rows]

    #    def stream_expressions(expressions, data, kind, titles=None):
    #        codes = []
    #        for i, expr in enumerate(expressions):
    #            try:
    #                code = get_code(expr)
    #            except (TypeError, SyntaxError) as e:
    #                data = {
    #                    'expr': expr,
    #                    'error': unicode(e)
    #                }
    #                if titles:
    #                    data['name'] = titles[i]
    #                else:
    #                    data['idx'] = i
    #                raise APIError(u'invalid %s expression' % kind, data=data)
    #            codes.append((code, expr))
    #        for row in data:
    #            new_row = []
    #            row_vars = dict(row)
    #            row_vars['query'] = kwargs
    #            for i, (code, expr) in enumerate(codes):
    #                try:
    #                    result = eval(code, {}, row_vars)
    #                except Exception as e:
    #                    data = {
    #                        'expr': expr,
    #                        'row': repr(row),
    #                    }
    #                    if titles:
    #                        data['name'] = titles[i]
    #                    else:
    #                        data['idx'] = i
    #                    raise APIError(u'invalid %s expression' % kind, data=data)
    #                new_row.append(result)
    #            yield new_row, row

    #    filters = query.get_list('filters')

    #    if filters:
    #        data = [row for new_row, row in stream_expressions(filters, data, kind='filters')
    #                if all(new_row)]

    #    order = query.get_list('order')
    #    if order:
    #        generator = stream_expressions(order, data, kind='order')
    #        new_data = [(tuple(new_row), row) for new_row, row in generator]
    #        new_data.sort(key=lambda (k, row): k)
    #        data = [row for key, row in new_data]

    #    distinct = query.get_list('distinct')
    #    if distinct:
    #        generator = stream_expressions(distinct, data, kind='distinct')
    #        seen = set()
    #        new_data = []
    #        for new_row, row in generator:
    #            new_row = tuple(new_row)
    #            try:
    #                hash(new_row)
    #            except TypeError:
    #                raise APIError(u'distinct value is unhashable',
    #                               data={
    #                                   'row': repr(row),
    #                                   'distinct': repr(new_row),
    #                               })
    #            if new_row in seen:
    #                continue
    #            new_data.append(row)
    #            seen.add(new_row)
    #        data = new_data

    #    projection = query.get_list('projections')
    #    if projection:
    #        expressions = []
    #        titles = []
    #        for mapping in projection:
    #            name, expr = mapping.split(':', 1)
    #            if not identifier_re.match(name):
    #                raise APIError(u'invalid projection name', data=name)
    #            titles.append(name)
    #            expressions.append(expr)
    #        new_data = []
    #        for new_row, row in stream_expressions(expressions, data, kind='projection',
    #                                               titles=titles):
    #            new_data.append(dict(zip(titles, new_row)))
    #        data = new_data

    #    # allow jsonp queries by select2
    #    # filtering is done there afater projection because we need a projection named text for
    #    # retro-compatibility with previous use of the csvdatasource with select2
    #    if 'q' in request.GET:
    #        if 'case-insensitive' in request.GET:
    #            filters = ["query['q'].lower() in text.lower()"]
    #        else:
    #            filters = ["query['q'] in text"]
    #        data = [row for new_row, row in stream_expressions(filters, data, kind='filters')
    #                if new_row[0]]

    #    if query.structure == 'array':
    #        return [[row[t] for t in titles] for row in data]
    #    elif query.structure == 'dict':
    #        return data
    #    elif query.structure == 'tuples':
    #        return [[[t, row[t]] for t in titles] for row in data]
    #    elif query.structure == 'onerow':
    #        if len(data) != 1:
    #            raise APIError('more or less than one row', data=data)
    #        return data[0]
    #    elif query.structure == 'one':
    #        if len(data) != 1:
    #            raise APIError('more or less than one row', data=data)
    #        if len(data[0]) != 1:
    #            raise APIError('more or less than one column', data=data)
    #        return data[0].values()[0]

## TODO
class GenericOperation(models.Model):
    foo = models.TextField()

class AddOperation(models.Model):
    distinguished_name = models.TextField('Distinguished name', blank=True, default=base,
            help_text='Distinguished name for the \'Add\' operation')
    object_class = models.TextField('Object class(es)', blank=True,
            help_text='List of object classes')
    attributes = models.TextField('Attributes', blank=True,
            help_text='Dictionary of attributes for the new entry')
    controls = models.TextField('Controls', blank=True,
            help_text='Optional request control parameters (see ldap3 doc)')

class DeleteOperation(models.Model):
    distinguished_name = models.TextField('Distinguished name', blank=True, default=base,
            help_text='Distinguished name for the \'Delete\' operation')
    controls = models.TextField('Controls', blank=True,
            help_text='Optional request control parameters (see ldap3 doc)')

class ModifyOperation(models.Model):
    distinguished_name = models.TextField('Distinguished name', blank=True, default=base,
            help_text='Distinguished name for the \'Modify\' operation')
    changes = models.TextField('Changes', blank=True,
            help_text='Dictionary of changes for the modified entry')
    controls = models.TextField('Controls', blank=True,
            help_text='Optional request control parameters (see ldap3 doc)')

class ModifyDNOperation(models.Model):
    distinguished_name = models.TextField('Distinguished name', blank=True, default=base,
            help_text='Distinguished name for the \'ModifyDN\' operation')
    relative_dn = models.TextField('Relative distinguished name', blank=True, default=base,
            help_text='Relative distinguished name for the \'ModifyDN\' operation')
    delete_old_dn = models.BooleanField('Delete old DN?', default=True)
    new_superior = models.TextField('New superior', blank=True, default=base,
            help_text='New container for the entry')
    controls = models.TextField('Controls', blank=True,
            help_text='Optional request control parameters (see ldap3 doc)')


class Query(models.Model):
    resource = models.ForeignKey(LDAPResource)
    slug = models.SlugField('Name (slug)')
    label = models.CharField('Label', max_length=100)
    description = models.TextField('Description', blank=True)
    #server = models.CharField('Server', max_length=200)
    #user = models.CharField('User', max_length=200)
    #password = models.CharField('Password', max_length=50)
    ldif_file = models.FileField('LDIF File', upload_to='ldif', blank=True) # For future uses
    distinguished_name = models.TextField('Distinguished name', blank=True, default=base,
            help_text='Distinguished name for the LDAP operation')
    filters = models.TextField('Filters', blank=True,
            help_text='List of filter clauses (LDAP RFC4515 expression)')
    attributes = models.TextField('Attributes', blank=False, default='ALL_ATTRIBUTES',
            help_text='List of attributes to retrieve for matching entries.')
    scope = models.CharField('Scope', choices=CHOICES, max_length=1,
            blank=False, default='1')
    operation = models.CharField('Operation type', choices=OPERATIONS, max_length=1,
            blank=False, default='1')

    #projections = models.TextField('Projections', blank=True,
    #        help_text='List of projections (name:expression)')
    #order = models.TextField('Order', blank=True,
    #        help_text='Ordering columns')
    #distinct = models.TextField('Distinct', blank=True,
    #        help_text='Distinct columns')
    #structure = models.CharField('Structure',
    #        max_length=20,
    #        choices=[
    #            ('array', 'Array'),
    #            ('dict', 'Dictionary'),
    #            ('tuples', 'Tuples'),
    #            ('onerow', 'Single Row'),
    #            ('one', 'Single Value')],
    #        default='dict',
    #        help_text='Data structure used for the response')

    class Meta:
        ordering = ['slug']

    def get_list(self, attribute):
        if not getattr(self, attribute):
            return []
        return getattr(self, attribute).strip().splitlines()
