#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import logging
import datetime
import random
from flask import url_for, current_app, redirect, request
from flask.ext.mistune import markdown

from quokka.core import TEXT_FORMATS
from quokka.core.db import db
from quokka.core.fields import MultipleObjectsReturned
from quokka.modules.accounts.models import User
from quokka.utils.text import slugify
from quokka.utils import get_current_user_for_models

from .admin.utils import _l

logger = logging.getLogger()


###############################################################
# Commom extendable base classes
###############################################################

class ContentFormat(object):
    content_format = db.StringField(
        choices=TEXT_FORMATS,
        default="html"  # TODO: Find a way to set default form settings
    )


class Dated(object):
    available_at = db.DateTimeField(default=datetime.datetime.now)
    available_until = db.DateTimeField(required=False)
    created_at = db.DateTimeField(default=datetime.datetime.now)
    updated_at = db.DateTimeField(default=datetime.datetime.now)


class Owned(object):
    created_by = db.ReferenceField(User)
    last_updated_by = db.ReferenceField(User)
    authors = db.ListField(db.ReferenceField(User))

    def get_authors(self, sortedf=None):
        return set(self.authors + [self.created_by])

    @property
    def has_multiple_authors(self):
        return len(self.get_authors()) > 1


class Publishable(Dated, Owned):
    published = db.BooleanField(default=False)

    def save(self, *args, **kwargs):
        self.updated_at = datetime.datetime.now()

        user = get_current_user_for_models()

        if not self.id and not self.created_by:
            self.created_by = user
        self.last_updated_by = user

        super(Publishable, self).save(*args, **kwargs)


class Slugged(object):
    slug = db.StringField(max_length=255, required=True)

    def validate_slug(self, title=None):
        if self.slug:
            self.slug = slugify(self.slug)
        else:
            self.slug = slugify(title or self.title)


class LongSlugged(Slugged):
    long_slug = db.StringField(unique=True, required=True)
    mpath = db.StringField()

    def _create_mpath_long_slug(self):
        if isinstance(self, Channel):
            if self.parent and self.parent != self:
                self.long_slug = "/".join(
                    [self.parent.long_slug, self.slug]
                )
                self.mpath = "".join(
                    [self.parent.mpath, self.slug, ',']
                )
            else:
                self.long_slug = self.slug
                self.mpath = ",%s," % self.slug
        elif isinstance(self, Content):
            self.long_slug = "/".join(
                [self.channel.long_slug, self.slug]
            )
            self.mpath = "".join([self.channel.mpath, self.slug, ','])

    def validate_long_slug(self):
        self._create_mpath_long_slug()

        filters = dict(long_slug=self.long_slug)
        if self.id:
            filters['id__ne'] = self.id

        exist = self.__class__.objects(**filters)
        if exist.count():
            if current_app.config.get('SMART_SLUG_ENABLED', False):
                self.slug = "{0}-{1}".format(self.slug, random.getrandbits(32))
                self._create_mpath_long_slug()
            else:
                raise db.ValidationError(
                    _l("%(slug)s slug already exists",
                       slug=self.long_slug)
                )


class Tagged(object):
    tags = db.ListField(db.StringField(max_length=50))


def default_formatter(value):
    return value


class CustomValue(db.EmbeddedDocument):

    FORMATS = (
        ('json', "json"),
        ('text', "text"),
        ('int', "int"),
        ('float', "float"),
    )

    DEFAULT_FORMATTER = default_formatter

    FORMATTERS = {
        'json': json.loads,
        'text': DEFAULT_FORMATTER,
        'int': int,
        'float': float
    }

    REVERSE_FORMATTERS = {
        'json': lambda val: val if isinstance(val, str) else json.dumps(val),
        'text': DEFAULT_FORMATTER,
        'int': DEFAULT_FORMATTER,
        'float': DEFAULT_FORMATTER
    }

    name = db.StringField(max_length=50, required=True)
    rawvalue = db.StringField(verbose_name=_l("Value"),
                              required=True)
    formatter = db.StringField(choices=FORMATS, default="text", required=True)

    @property
    def value(self):
        return self.FORMATTERS.get(self.formatter,
                                   self.DEFAULT_FORMATTER)(self.rawvalue)

    @value.setter
    def value(self, value):
        self.rawvalue = self.REVERSE_FORMATTERS.get(self.formatter,
                                                    self.STR_FORMATTER)(value)

    def clean(self):
        try:
            self.value
        except Exception as e:
            # raise base exception because Flask-Admin can't handle the output
            # for some specific Exceptions of Mongoengine
            raise Exception(e.message)
        super(CustomValue, self).clean()

    def __unicode__(self):
        return u"{s.name} -> {s.value}".format(s=self)


class HasCustomValue(object):
    values = db.ListField(db.EmbeddedDocumentField(CustomValue))

    def get_values_tuple(self):
        return [(value.name, value.value, value.formatter)
                for value in self.values]

    def clean(self):
        current_names = [value.name for value in self.values]
        for name in current_names:
            if current_names.count(name) > 1:
                raise Exception(_l("%(name)s already exists",
                                   name=name))
        super(HasCustomValue, self).clean()


class Ordered(object):
    order = db.IntField(required=True, default=1)


class ChannelConfigs(object):
    content_filters = db.DictField(required=False, default=lambda: {})
    inherit_parent = db.BooleanField(default=True)


class TemplateType(HasCustomValue):
    title = db.StringField(max_length=255, required=True)
    identifier = db.StringField(max_length=255, required=True, unique=True)
    template_suffix = db.StringField(max_length=255, required=True)
    theme_name = db.StringField(max_length=255, required=False)

    def __unicode__(self):
        return self.title


class ChannelType(TemplateType, ChannelConfigs, db.DynamicDocument):
    """Define the channel template type and its filters"""


class ContentProxy(db.DynamicDocument):
    content = db.GenericReferenceField(required=True, unique=True)

    def __unicode__(self):
        return self.content.title


class Channel(Tagged, HasCustomValue, Publishable, LongSlugged,
              ChannelConfigs, ContentFormat, db.DynamicDocument):
    title = db.StringField(max_length=255, required=True)
    description = db.StringField()
    show_in_menu = db.BooleanField(default=False)