from datetime import datetime
from typing import List, Optional

import attr
import cattr
from dateutil.parser import parse


class TestCAttrs:
    package = 'attrs + cattrs'
    version = attr.__version__

    def __init__(self, allow_extra):
        # cf. https://github.com/Tinche/cattrs/issues/26 why at least structure_str is needed
        def structure_str(s, _):
            if not isinstance(s, str):
                raise ValueError()
            return s

        def structure_int(i, _):
            if not isinstance(i, int):
                raise ValueError()
            return i

        class PositiveInt(int):
            ...

        def structure_posint(i, x):
            i = PositiveInt(i)
            if not isinstance(i, PositiveInt):
                raise ValueError()
            if i <= 0:
                raise ValueError()
            return i

        cattr.register_structure_hook(datetime, lambda isostring, _: parse(isostring))
        cattr.register_structure_hook(str, structure_str)
        cattr.register_structure_hook(int, structure_int)
        cattr.register_structure_hook(PositiveInt, structure_posint)

        def str_len_val(max_len: int, min_len: int = 0, required: bool = False):
            # validate the max len of a string and optionally its min len and whether None is
            # an acceptable value
            def _check_str_len(self, attribute, value):
                if value is None:
                    if required:
                        raise ValueError("")
                    else:
                        return
                if len(value) > max_len:
                    raise ValueError("")
                if min_len and len(value) < min_len:
                    raise ValueError("")

            return _check_str_len

        def pos_int(self, attribute, value):
            # Validate that value is a positive >0 integer; None is allowed
            if value is None:
                return
            if value <= 0:
                raise ValueError("")

        @attr.s(auto_attribs=True, frozen=True, kw_only=True)
        class Skill:
            subject: str
            subject_id: int
            category: str
            qual_level: str
            qual_level_id: int
            qual_level_ranking: float = 0

        @attr.s(auto_attribs=True, frozen=True, kw_only=True)
        class Location:
            latitude: float = None
            longitude: float = None

        @attr.s(auto_attribs=True, frozen=True, kw_only=True)
        class Model:
            id: int
            sort_index: float
            client_name: str = attr.ib(validator=str_len_val(255))
            # client_email: EmailStr = None
            client_phone: Optional[str] = attr.ib(default=None, validator=str_len_val(255))
            location: Optional[Location] = None

            contractor: Optional[PositiveInt]
            upstream_http_referrer: Optional[str] = attr.ib(default=None, validator=str_len_val(1023))
            grecaptcha_response: str = attr.ib(validator=str_len_val(1000, 20, required=True))
            last_updated: Optional[datetime] = None
            skills: List[Skill] = []

        self.model = Model

    def validate(self, data):
        try:
            return True, cattr.structure(data, self.model)
        except ValueError as e:
            return False, str(e)
        except TypeError as e:
            return False, str(e)
