# -*- coding: utf-8 -*-
import abc
import os
import json

from datetime import datetime
from werkzeug.exceptions import NotFound

from settings.schemas import APIROLE_SCHEMA, APIUSER_SCHEMA
from .managers import BaseManager
from .utils import string_to_utc_date
from .validator import Validator
from .utils import json_serializer


class ValidationError(Exception):
    pass


class staticproperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()


class Model(object):
    __metaclass__ = abc.ABCMeta
    manager_class = BaseManager
    _manager = None
    _data = None

    @staticproperty
    @classmethod
    def manager(cls):
        if cls._manager is None:
            cls._manager = cls.manager_class(cls)

        return cls._manager

    def __init__(self, data, validate=True, *args, **kwargs):
        if validate:
            self.validate(data)
        else:
            self._data = data

    def save(self):
        return self.manager.save(self)

    @abc.abstractmethod
    def create_validator(self):
        pass

    def validate(self, data):
        validator = self.create_validator()

        if not validator.validate(data):
            raise ValidationError(validator.errors)
        self._data = validator.document

    def update_data(self, new_data):
        for field in new_data.keys():
            self.data[field] = new_data.get(field)

        self.validate(self._data)

    @abc.abstractproperty
    def key(self):
        pass

    @property
    def data(self):
        return self._data

    @property
    def clear_data(self):
        """
        Used to display the information to the public.
        """
        return json.loads(json.dumps(self._data, default=json_serializer))

    def __hash__(self):
        return hash(self.__class__.__name__ + self.key)

    def __eq__(self, other):
        return hash(self) == hash(other)

    def __ne__(self, other):
        return not self.__eq__(other)


class APIRole(Model):
    """
    APIRole model with the following schema:
    {
        'name': {'type': 'url_safe_string', 'required': True, 'empty': False},
        'is_active': {'type': 'boolean', 'default': True},
        'valid_until': {
            'type': 'optional_datetime_string',
            'nullable': True,
            'coerce': coerce_string_to_utc_string,
            'default': None
        },
        'permissions': {'type': 'dict', 'required': True}
    }

    Example:
    {
        'valid_until': None,
        'is_active': True,
        'name': 'admin',
        'permissions': {}
    }
    """

    def create_validator(self):
        return Validator(APIROLE_SCHEMA, allow_unknown=True)

    @property
    def permissions(self):
        return self.data['permissions']

    @property
    def is_active(self):
        return self.data['is_active']

    @property
    def key(self):
        return self.data['name']

    def is_valid(self):
        if not self.data['valid_until']:
            return True
        return string_to_utc_date(self.data['valid_until']) > string_to_utc_date()


class APIUser(Model):
    """
    APIUser model with the following schema:
    {
        'name': {'type': 'url_safe_string', 'required': True, 'empty': False},
        'key': {'type': 'string', 'required': False, 'empty': False, 'nullable': True},
        'is_active': {'type': 'boolean', 'default': True},
        'is_superuser' : {'type': 'boolean', 'default': False},
        'last_login': {'type': 'datetime'},
        'roles': {
            'type': 'list',
            'required': False,
            'default': [],
            'schema': {
                'type': 'url_safe_string',
                'required': True,
                'empty': False
            }
        }
    }

    Example:
    {
        'roles': ['admin'],
        'is_active': True,
        'name': 'Cano',
        'key': '$6$rounds=711089$Kmwk6pUt3Xtohq1j$6uTInfXXYu6VxC',
        'last_login': '2017-12-07T17:29:30.249903Z'
    }
    """

    def __init__(self, data, validate=True):
        data = self._initialize_data(data)
        super(APIUser, self).__init__(data, validate)

    def _initialize_data(self, data):

        if 'key' not in data:
            data['key'] = APIUser.generate_key()

        if 'last_login' in data and type(data['last_login']) is not datetime:
            data['last_login'] = string_to_utc_date(data['last_login'])

        return data

    def create_validator(self):
        return Validator(APIUSER_SCHEMA, allow_unknown=True)

    def validate(self, data):
        super(APIUser, self).validate(data)

        # Use self.data instead of data, which is normalized by Cerberus
        for role in self.data['roles']:
            try:
                APIRole.manager.get(role)
            except NotFound:
                raise ValidationError(
                    {'roles': 'Role not found: {}'.format(role)})

    @property
    def key(self):
        return self.data['name']

    @property
    def roles(self):
        return self.data['roles']

    @property
    def is_active(self):
        return self.data['is_active']

    @property
    def is_superuser(self):
        return self.data['is_superuser']

    @property
    def last_login(self):
        return self.data['last_login']

    def verify_password(self, password):
        try:
            return password == self.data['key']
        except ValueError:
            return False

    @staticmethod
    def generate_key():
        """
        Create a good-enough random number to use as API Key.
        """
        return os.urandom(40).encode('hex')[:40]
