# -*- coding: utf-8 -*-

from __future__ import absolute_import, print_function, unicode_literals

from functools import wraps

import arrow
from arrow.parser import ParserError
from marshmallow import ValidationError, fields
from marshmallow.utils import missing as missing_

from prospyr.util import import_dotted_path


class Unix(fields.Field):
    """
    datetime.datetime <-> unix timestamp
    """
    def _serialize(self, value, attr, obj):
        try:
            return arrow.get(value).timestamp
        except ParserError as ex:
            raise ValidationError(ex)

    def _deserialize(self, value, attr, obj):
        try:
            return arrow.get(value).datetime
        except ParserError as ex:
            raise ValidationError(ex)


def normalise_many(fn, default=False):
    """
    Turn single values into lists if self.many = True.

    Use to wrap the _serialize and _deserialize methods of Field subclasses.
    These methods normally have the signature (self, value, attr, data); once
    wrapped they can assume `value` is a collection. From there, (self, values,
    attr, data) makes more sense as the signature.
    """
    @wraps(fn)
    def wrapper(self, value, attr, data):
        many = getattr(self, 'many', default)
        if not many:
            value = [value]
        res = fn(self, value, attr, data)
        if not many:
            return res[0]
        else:
            return res
    return wrapper


class NestedResource(fields.Field):
    """
    Represent a nested data structure as a Resource instance.

    If many=True, a listlike data structure is expected instead. If
    id_only=True, only the id field must exist in the nested representation;
    the remainder of the Resource's data will be fetched.
    """

    def __init__(self, resource_cls, default=missing_, many=False,
                 id_only=False, **kwargs):
        self.resource_cls = resource_cls
        self.schema = type(resource_cls.Meta.schema)
        self.many = many
        self.id_only = id_only
        super(NestedResource, self).__init__(default=default, many=many,
                                             **kwargs)

    @normalise_many
    def _deserialize(self, values, attr, data):
        resources = []
        for value in values:
            if self.id_only:
                resources.append(self.resource_cls.objects.get(id=value['id']))
            else:
                resources.append(self.resource_cls.from_api_data(value))
        return resources

    @normalise_many
    def _serialize(self, values, attr, data):
        return [value._raw_data for value in values]


class NestedIdentifiedResource(fields.Field):
    """
    (De)serialize "Identifier" fields as Resource instances.

    See:
        https://www.prosperworks.com/developer_api/identifier
    """

    _default_types = {
        'person': 'prospyr.resources.Person',
        # 'lead': 'Lead',  # TODO
        'opportunity': 'prospyr.resources.Opportunity',
        'company': 'prospyr.resources.Company',
    }

    def __init__(self, default=missing_, many=False, types=None, **kwargs):
        self.many = many
        self.types = self._default_types if types is None else types
        self.reverse_types = {v: k for k, v in self.types.items()}
        super(NestedIdentifiedResource, self).__init__(default=default,
                                                       many=many, **kwargs)

    @normalise_many
    def _deserialize(self, values, attr, data):
        resources = []
        for value in values:
            idtype = value['type']
            if idtype is None and self.allow_none is False:
                self.fail('null')
            elif idtype is None:
                resource = None
            else:
                resource_cls = import_dotted_path(self.types.get(idtype))
                if resource_cls is None:
                    raise ValueError('Unknown identifier type %s' % idtype)
                resource = resource_cls.objects.get(id=value['id'])
            resources.append(resource)
        return resources

    @normalise_many
    def _serialize(self, values, attr, data):
        from prospyr.resources import Identifier  # avoid circular derp

        raws = []
        for value in values:
            if value is None and self.allow_none is False:
                self.fail('null')
            elif value is None:
                raw = {'type': None, 'id': None}
            else:
                raw = Identifier.from_instance(value)._raw_data
            raws.append(raw)
        return raws
