from functools import wraps

import phonenumbers
from flask import flash, abort
from flask_login import current_user
from phonenumbers.phonenumberutil import is_possible_number
from wtforms import ValidationError

from flaskshop.constant import Permission


class PhoneNumber(phonenumbers.PhoneNumber):
    """
    A extended version of phonenumbers.PhoneNumber that provides
    some neat and more pythonic, easy to access methods. This makes using a
    PhoneNumber instance much easier, especially in templates and such.
    """

    format_map = {
        "E164": phonenumbers.PhoneNumberFormat.E164,
        "INTERNATIONAL": phonenumbers.PhoneNumberFormat.INTERNATIONAL,
        "NATIONAL": phonenumbers.PhoneNumberFormat.NATIONAL,
        "RFC3966": phonenumbers.PhoneNumberFormat.RFC3966,
    }

    @classmethod
    def from_string(cls, phone_number, region=None):
        phone_number_obj = cls()
        if region is None:
            region = None
        phonenumbers.parse(
            number=phone_number,
            region=region,
            keep_raw_input=True,
            numobj=phone_number_obj,
        )
        return phone_number_obj

    def __unicode__(self):
        format_string = "E164"
        fmt = self.format_map[format_string]
        return self.format_as(fmt)

    def is_valid(self):
        """
        checks whether the number supplied is actually valid
        """
        return phonenumbers.is_valid_number(self)

    def format_as(self, format):
        return phonenumbers.format_number(self, format)

    @property
    def as_international(self):
        return self.format_as(phonenumbers.PhoneNumberFormat.INTERNATIONAL)

    @property
    def as_e164(self):
        return self.format_as(phonenumbers.PhoneNumberFormat.E164)

    @property
    def as_national(self):
        return self.format_as(phonenumbers.PhoneNumberFormat.NATIONAL)

    @property
    def as_rfc3966(self):
        return self.format_as(phonenumbers.PhoneNumberFormat.RFC3966)

    def __len__(self):
        return len(self.__unicode__())

    def __eq__(self, other):
        """
        Override parent equality because we store only string representation
        of phone number, so we must compare only this string representation
        """
        if (
            isinstance(other, PhoneNumber)
            or isinstance(other, phonenumbers.PhoneNumber)
            or isinstance(other, str)
        ):
            format_string = "E164"
            default_region = None
            fmt = self.format_map[format_string]
            if isinstance(other, str):
                # convert string to phonenumbers.PhoneNumber
                # instance
                try:
                    other = phonenumbers.parse(other, region=default_region)
                except phonenumbers.NumberParseException:
                    # Conversion is not possible, thus not equal
                    return False
            other_string = phonenumbers.format_number(other, fmt)
            return self.format_as(fmt) == other_string
        else:
            return False

    def __hash__(self):
        return hash(self.__unicode__())


def to_python(value):
    if value in (None, ""):  # None or ''
        phone_number = value
    elif value and isinstance(value, str):
        try:
            phone_number = PhoneNumber.from_string(phone_number=value)
        except phonenumbers.NumberParseException:
            # the string provided is not a valid PhoneNumber.
            phone_number = PhoneNumber(raw_input=value)
    elif isinstance(value, phonenumbers.PhoneNumber) and not isinstance(
        value, PhoneNumber
    ):
        phone_number = PhoneNumber()
        phone_number.merge_from(value)
    elif isinstance(value, PhoneNumber):
        phone_number = value
    else:
        # TODO: this should somehow show that it has invalid data, but not
        #       completely die for bad data in the database.
        #       (Same for the NumberParseException above)
        phone_number = None
    return phone_number


def validate_possible_number(value):
    phone_number = to_python(value)
    if phone_number and not is_possible_number(phone_number):
        raise ValidationError("The phone number entered is not valid.")


def permission_required(permission):
    def decorator(f):
        @wraps(f)
        def _deco(*args, **kwargs):
            if current_user.is_authenticated and current_user.can(permission):
                return f(*args, **kwargs)
            abort(403)

        return _deco

    return decorator


def admin_required(f):
    return permission_required(Permission.ADMINISTER)(f)