import json
from datetime import datetime
from typing import Optional, Callable, Any

from dateutil.parser import parse
from dateutil.tz import tzutc


class BaseParser(object):
    def __init__(self, invalid_exception_type, missing_exception_type):
        self.invalid_exception_type = invalid_exception_type
        self.missing_exception_type = missing_exception_type

    @staticmethod
    def not_empty(n):
        # type: (str) -> bool
        return len(n) > 0

    @staticmethod
    def str_to_bool(arg):
        # type: (str) -> bool
        if type(arg) == bool:
            return arg
        return not (arg is None) and arg.lower() == "true"

    @staticmethod
    def datetime_to_str(date):
        # type: (datetime) -> str
        if not date:
            return ""
        return date.replace(microsecond=0).isoformat()

    @staticmethod
    def str_to_datetime(string):
        # type: (str) -> Optional[datetime]
        if not string:
            return None
        ret = parse(string)
        if ret.tzinfo is not None:
            return ret.astimezone(tzutc())
        return ret

    @staticmethod
    def has_timezone_info(arg):
        indicator = arg[-5]
        return indicator == "-" or indicator == "+"

    @staticmethod
    def is_uuid(arg):
        # type: (str) -> bool
        return len(arg) == 36

    @staticmethod
    def is_json(arg):
        # type: (str) -> bool
        try:
            json.loads(arg)
        except ValueError:
            return False

        return True

    @staticmethod
    def check_missing(items, required_fields):
        if not items:
            return required_fields if required_fields and len(required_fields) > 0 else []
        return [item for item in required_fields if item not in items or items[item] is None or items[item] == '']

    @staticmethod
    def decode_body(body):
        body_decoded = body.decode("utf-8")
        data = json.loads(body_decoded if body_decoded is not None and body_decoded != "" else "{}")
        return data

    def parse_if_valid(self, properties, name, cast, condition=None, required=False):
        # type: (dict, str, Callable[[Any], Any], Callable[[Any], Any], Optional[bool]) -> Any
        if name not in properties or properties[name] is None:
            if required:
                raise self.missing_exception_type([name])
            else:
                return None

        try:
            parsed_value = cast(properties[name])
            if condition and not condition(parsed_value):
                raise self.invalid_exception_type(name)
        except:
            raise self.invalid_exception_type(name)
        return parsed_value

    def raise_if_item_is_missing(self, data, required_fields):
        missing_items = self.check_missing(data, required_fields)
        if missing_items:
            raise self.missing_exception_type(missing_items)
