"""Functions used to extract required data from web form."""

import settings
from libs import iredutils, iredpwd
from libs.l10n import TIMEZONES


# Return single value of specified form name.
def get_single_value(form,
                     input_name,
                     default_value='',
                     is_domain=False,
                     is_email=False,
                     is_integer=False,
                     is_strict_ip=False,
                     is_ip_or_network=False,
                     to_lowercase=False,
                     to_uppercase=False,
                     to_string=False,
                     split_value=False,
                     split_separator=None,
                     strip_str_before_split=False,
                     strip_str=None):
    v = form.get(input_name, '')
    if not v:
        v = default_value

    if not isinstance(v, (int, float)):
        try:
            v = v.strip()
        except:
            pass

    if is_domain:
        if not iredutils.is_domain(v):
            return ''

    if is_email:
        if not iredutils.is_email(v):
            v = default_value

    if is_integer:
        try:
            v = int(v)
        except:
            v = default_value

    if is_strict_ip:
        if not iredutils.is_strict_ip(v):
            return ''

    if is_ip_or_network:
        if not iredutils.is_ip_or_network(v):
            return ''

    if to_string:
        try:
            if isinstance(v, (list, tuple)):
                v = [str(i) for i in v]
            else:
                v = str(v)
        except:
            pass

    if to_lowercase:
        if isinstance(v, (list, tuple)):
            v = [i.lower() for i in v]
        else:
            v = v.lower()

    if to_uppercase:
        if isinstance(v, (list, tuple)):
            v = [i.upper() for i in v]
        else:
            v = v.upper()

    if split_value:
        # return a list
        if isinstance(v, str):
            if strip_str_before_split:
                if not strip_str:
                    strip_str = ' '

                v.strip(strip_str)

            if split_separator:
                v = v.split(split_separator)
            else:
                v = v.split()

            # Remove empty values
            v = [i for i in v if i]

    return v


# Return single value of specified form name.
def get_multi_values(form,
                     input_name,
                     default_value=None,
                     input_is_textarea=False,
                     is_domain=False,
                     is_email=False,
                     is_ip_or_network=False,
                     to_lowercase=False,
                     to_uppercase=False,
                     to_string=False):
    v = form.get(input_name)
    if v:
        if input_is_textarea:
            v = v.splitlines()
            v = [i.strip() for i in v]
    else:
        if default_value is None:
            v = []
        else:
            v = default_value

    # Remove duplicate items.
    try:
        v = list(set(v))
    except:
        v = []

    if is_domain:
        v = [str(i).lower() for i in v if iredutils.is_domain(i)]

    if is_email:
        v = [str(i).lower() for i in v if iredutils.is_email(i)]

    if is_ip_or_network:
        v = [str(i) for i in v if iredutils.is_ip_or_network(i)]

    if to_lowercase:
        if not (is_domain or is_email):
            v = [i.lower() for i in v]

    if to_uppercase:
        if not (is_domain or is_email):
            v = [i.upper() for i in v]

    if to_string:
        v = [str(i) for i in v]

    v.sort()
    return v


def get_multi_values_from_api(form,
                              input_name,
                              to_string=True,
                              to_lowercase=True,
                              is_domain=False,
                              is_email=False):
    """Param/value posted from API will be: key=value1,value2,value3,...
    This function extract values and return them as a list.
    """
    values = get_single_value(form=form,
                              input_name=input_name,
                              to_string=to_string,
                              to_lowercase=to_lowercase,
                              split_value=True,
                              split_separator=',',
                              strip_str_before_split=True)

    if is_domain:
        values = [i for i in values if iredutils.is_domain(i)]

    if is_email:
        values = [i for i in values if iredutils.is_email(i)]

    return list(set(values))


def get_multi_values_from_textarea(form,
                                   input_name,
                                   is_domain=False,
                                   is_email=False,
                                   to_lowercase=False):
    """Param/value posted from API will be: key=value1,value2,value3,...
    This function extract values and return them as a list.
    """
    v = get_single_value(form=form,
                         input_name=input_name,
                         to_string=True,
                         to_lowercase=to_lowercase,
                         split_value=True,
                         split_separator=None,
                         strip_str_before_split=True)

    if is_domain:
        v = [i for i in v if iredutils.is_domain(i)]

    if is_email:
        v = [i for i in v if iredutils.is_email(i)]

    return v


def get_form_dict(form,
                  input_name,
                  key_name=None,
                  multi_values=False,
                  default_value='',
                  input_is_textarea=False,
                  is_domain=False,
                  is_email=False,
                  is_integer=False,
                  to_lowercase=False,
                  to_uppercase=False,
                  to_string=False):
    d = {}
    if input_name in form:
        if multi_values:
            # Value is a list
            v = get_multi_values(form,
                                 input_name,
                                 default_value=default_value,
                                 input_is_textarea=input_is_textarea,
                                 is_domain=is_domain,
                                 is_email=is_email,
                                 to_lowercase=to_lowercase,
                                 to_uppercase=to_uppercase)
        else:
            v = get_single_value(form,
                                 input_name=input_name,
                                 default_value=default_value,
                                 is_domain=is_domain,
                                 is_email=is_email,
                                 is_integer=is_integer,
                                 to_lowercase=to_lowercase,
                                 to_uppercase=to_uppercase,
                                 to_string=to_string)

        # Convert values of some parameters
        if settings.backend == 'ldap':
            if input_name == 'accountStatus':
                # When 'accountStatus' is used by a checkbox, its value will
                # be 'on' which means the checkbox is checked.
                if v in ['enable', 'active', 'yes', 'on', 1]:
                    v = 'active'
                else:
                    v = 'disabled'
            elif input_name == 'isGlobalAdmin':
                if v != 'yes':
                    v = None
            elif input_name in ['quota', 'defaultQuota', 'maxUserQuota',
                                'minPasswordLength', 'maxPasswordLength',
                                'numberOfUsers', 'numberOfAliases',
                                'numberOfLists']:
                # Require integer number
                try:
                    v = int(v)
                except:
                    # Don't return any value.
                    return {}

        else:
            if input_name in ['accountStatus', 'backupmx']:
                if v in ['enable', 'active', 'yes', 1]:
                    v = 1
                else:
                    v = 0

        if key_name:
            d[key_name] = v
        else:
            if settings.backend == 'ldap':
                # Map some input names to LDAP attribute names
                # Warning: do not map the key names used in accountSetting.
                if input_name == 'name':
                    key_name = 'cn'
                elif input_name == 'accountStatus':
                    key_name = input_name
                elif input_name == 'language':
                    key_name = 'preferredLanguage'
                elif input_name == 'transport':
                    key_name = 'mtaTransport'
                else:
                    key_name = input_name
            else:
                key_name = input_name

            d[key_name] = v

    return d


def get_name(form, input_name='cn'):
    return get_single_value(form,
                            input_name=input_name,
                            default_value='')


def get_domain_name(form, input_name='domainName'):
    return get_single_value(form,
                            input_name=input_name,
                            default_value='',
                            is_domain=True,
                            to_lowercase=True,
                            to_string=True)


def get_domain_names(form, input_name='domainName'):
    return get_multi_values(form,
                            input_name=input_name,
                            default_value=[],
                            is_domain=True,
                            to_lowercase=True)


# Get default language for new mail user from web form.
def get_language(form, input_name='preferredLanguage'):
    lang = get_single_value(form, input_name=input_name, to_string=True)
    if lang not in iredutils.get_language_maps():
        lang = ''

    return lang


def get_domain_quota_and_unit(form,
                              input_quota='domainQuota',
                              input_quota_unit='domainQuotaUnit',
                              convert_to_mb=True):
    """Get domain quota and quota unit from web form, return a dict contains
    quota (in MB) and ORIGINAL quota unit: {'quota': <integer>, 'unit': <string>}.
    """
    # multiply is used for SQL backends.
    quota = str(form.get(input_quota))
    if quota.isdigit():
        quota = abs(int(quota))
    else:
        quota = 0

    quota_unit = str(form.get(input_quota_unit, 'MB'))
    if quota > 0:
        # Convert to MB
        if convert_to_mb:
            if quota_unit == 'GB':
                quota = quota * 1024
            elif quota_unit == 'TB':
                quota = quota * 1024 * 1024

    return {'quota': quota, 'unit': quota_unit}


# Get mailbox quota (in MB).
def get_quota(form, input_name='defaultQuota', default=0):
    quota = str(form.get(input_name))
    if quota.isdigit():
        quota = abs(int(quota))

        if input_name == 'maxUserQuota':
            quota_unit = str(form.get('maxUserQuotaUnit', 'MB'))
            if quota_unit == 'TB':
                quota = quota * 1024 * 1024
            elif quota_unit == 'GB':
                quota = quota * 1024
            else:
                # MB
                pass
    else:
        quota = default

    return quota


def get_account_status(form,
                       input_name='accountStatus',
                       default_value='active',
                       to_integer=False):
    status = get_single_value(form, input_name=input_name, to_string=True)

    if status not in ['active', 'disabled']:
        status = default_value

    # SQL backends store the account status as `active=[1|0]`
    # LDAP backends store the account status as `accountStatus=[active|disabled]`
    if to_integer:
        if status == 'active':
            return 1
        else:
            return 0
    else:
        return status


def get_password(form,
                 input_name='newpw',
                 confirm_pw_input_name='confirmpw',
                 min_passwd_length=None,
                 max_passwd_length=None):
    pw = get_single_value(form,
                          input_name=input_name,
                          to_string=True)

    confirm_pw = get_single_value(form,
                                  input_name=confirm_pw_input_name,
                                  to_string=True)

    qr = iredpwd.verify_new_password(newpw=pw,
                                     confirmpw=confirm_pw,
                                     min_passwd_length=min_passwd_length,
                                     max_passwd_length=max_passwd_length)
    if not qr[0]:
        return qr

    if 'store_password_in_plain_text' in form and settings.STORE_PASSWORD_IN_PLAIN:
        pw_hash = iredpwd.generate_password_hash(pw, pwscheme='PLAIN')
    else:
        pw_hash = iredpwd.generate_password_hash(pw)

    return True, {'pw_plain': pw, 'pw_hash': pw_hash}


def get_timezone(form, input_name='timezone'):
    tz = get_single_value(form,
                          input_name=input_name,
                          to_string=True)

    if tz in TIMEZONES:
        return tz

    return None


def get_list_access_policy(form,
                           input_name='accessPolicy',
                           default_value='public'):
    policy = get_single_value(form=form,
                              input_name=input_name,
                              default_value=default_value,
                              to_string=True)

    if policy not in iredutils.MAILLIST_ACCESS_POLICIES:
        policy = 'public'

    return policy


# iRedAPD: Get throttle setting for
def get_throttle_setting(form, account, inout_type='inbound'):
    # inout_type -- inbound, outbound.
    var_enable_throttle = 'enable_%s_throttling' % inout_type

    # not enabled.
    if var_enable_throttle not in form:
        return {}

    # name of form <input> tag:
    # [inout_type]_[name]
    # custom_[inout_type]_[name]

    # Pre-defined values
    setting = {'account': account,
               'priority': iredutils.get_account_priority(account),
               'period': 0,
               'max_msgs': 0,
               'max_quota': 0,
               'msg_size': 0,
               'kind': inout_type}

    input_keys = ['period', 'max_msgs', 'max_quota', 'msg_size']

    if inout_type == "outbound":
        setting["max_rcpts"] = 0
        input_keys.append("max_rcpts")

    for k in input_keys:
        var = inout_type + '_' + k

        # Get pre-defined value first
        v = form.get(var, '')

        if v == 'on':
            # Get custom value if it's not pre-defined
            v = form.get('custom_' + var)

        try:
            v = int(v)
            setting[k] = v
        except:
            continue

    # Return empty dict if all values are 0.
    return setting


# NOTE: used by LDAP backends.
def update_domain_creation_settings(form,
                                    account_settings,
                                    check_creation_permission=True):
    """Update `account_settings` with data from form.

    :param form: web form data
    :param account_settings: dict of per-admin account settings
    :param check_creation_permission: check whether html tag
        "<input name='allowed_to_create_domain' ... />" exists, used in user
        profile page.
    """
    _allowed = True
    if check_creation_permission:
        if 'allowed_to_create_domain' not in form:
            _allowed = False

    if _allowed:
        for i in ['create_max_domains',
                  'create_max_quota',
                  'create_max_users',
                  'create_max_aliases',
                  'create_max_lists']:
            if i in form:
                try:
                    v = int(form.get(i, '0'))
                except:
                    v = 0

                if v > 0:
                    account_settings[i] = v
                else:
                    if i in account_settings:
                        account_settings.pop(i)

        for i in ['disable_domain_ownership_verification']:
            if i in form:
                account_settings[i] = 'yes'
            else:
                if i in account_settings:
                    account_settings.pop(i)

        if 'create_max_quota' in account_settings:
            if 'create_quota_unit' in form:
                v = form.get('create_quota_unit', 'TB')
                if v in ['TB', 'GB']:
                    account_settings['create_quota_unit'] = v
                else:
                    if 'create_quota_unit' in account_settings:
                        account_settings.pop('create_quota_unit')

        for i in ['create_max_domains',
                  'create_max_quota',
                  'create_max_users',
                  'create_max_aliases',
                  'create_max_lists']:
            if i in account_settings:
                account_settings['create_new_domains'] = 'yes'
                break
            else:
                # Remove account_settings['create_new_domains']
                try:
                    account_settings.pop('create_new_domains')
                except:
                    pass
    else:
        for i in ['create_new_domains',
                  'create_max_domains',
                  'create_max_quota',
                  'create_max_users',
                  'create_max_aliases',
                  'create_max_lists',
                  'disable_domain_ownership_verification']:
            if i in account_settings:
                account_settings.pop(i)

    return account_settings


# NOTE: used by LDAP backends.
def get_domain_creation_settings(form):
    """Get per-admin domain creation limits from web form."""
    d = {}

    kv = get_form_dict(form=form,
                       input_name='create_max_domains',
                       default_value=0,
                       is_integer=True)
    if kv:
        d.update(kv)
        d['create_new_domains'] = 'yes'

        kv = get_form_dict(form=form,
                           input_name='create_max_users',
                           default_value=0,
                           is_integer=True)
        d.update(kv)

        kv = get_form_dict(form=form,
                           input_name='create_max_aliases',
                           default_value=0,
                           is_integer=True)
        d.update(kv)

        kv = get_form_dict(form=form,
                           input_name='create_max_lists',
                           default_value=0,
                           is_integer=True)
        d.update(kv)

        # format: 10TB, 10GB, 10MB.
        kv = get_form_dict(form=form,
                           input_name='create_max_quota',
                           default_value=0,
                           is_integer=True)

        if kv:
            _kv = get_form_dict(form=form,
                                input_name='create_quota_unit',
                                default_value='MB',
                                to_uppercase=True,
                                to_string=True)
            d.update(_kv)
            d.update(kv)

    # Discard item which has value == '0'
    if d:
        for (k, v) in list(d.items()):
            if v == 0:
                d.pop(k)

                if k == 'create_max_quota':
                    if 'create_quota_unit' in d:
                        d.pop('create_quota_unit')

    return d


#
# mlmmj
#
def get_mlmmj_params_from_web_form(form):
    """Convert parameter names/values in web form to mlmmj parameters."""
    mlmmj_params = form.copy()

    # Remove parameters used by web form but not mlmmjadmin API
    for k in ['csrf_token', 'modified', 'active', 'accountStatus']:
        if k in mlmmj_params:
            mlmmj_params.pop(k)

    #
    # Get access policy
    #
    if 'accessPolicy' in form:
        access_policy = form.get('accessPolicy', '').lower()
        mlmmj_params.pop('accessPolicy')
    else:
        access_policy = 'public'

    if access_policy not in iredutils.ML_ACCESS_POLICIES:
        access_policy = 'public'

    mlmmj_params['access_policy'] = access_policy
    mlmmj_params['only_subscriber_can_post'] = 'no'
    mlmmj_params['only_moderator_can_post'] = 'no'
    if access_policy == 'membersonly':
        mlmmj_params['only_subscriber_can_post'] = 'yes'
    elif access_policy == 'moderatorsonly':
        mlmmj_params['only_moderator_can_post'] = 'yes'

    #
    # Get max message size (in bytes)
    #
    mlmmj_params['max_message_size'] = 0

    # `max_mail_size` and `max_mail_size_unit` are used by web form.
    _size = form.get('max_mail_size', 0)
    _unit = form.get('max_mail_size_unit', 'KB')
    try:
        _size = int(_size)
    except:
        pass

    if _size:
        if _unit == 'KB':
            mlmmj_params['max_message_size'] = _size * 1024
        elif _unit == 'MB':
            mlmmj_params['max_message_size'] = _size * 1024 * 1024

    if 'max_mail_size' in mlmmj_params:
        mlmmj_params.pop('max_mail_size')

    if 'max_mail_size_unit' in mlmmj_params:
        mlmmj_params.pop('max_mail_size_unit')

    # Other radio/checkbox options.
    for (k, v) in list(mlmmj_params.items()):
        # mlmmjadmin API expects values in 'yes', 'no'.
        if v == 'on':
            mlmmj_params[k] = 'yes'

        # Rename 'hidden_<key>' to '<key>'
        if k.startswith('hidden_'):
            nk = k.replace('hidden_', '')   # don't use `string.lstrip()`
            mlmmj_params.pop(k)

            if nk not in mlmmj_params:
                mlmmj_params[nk] = 'no'

    return mlmmj_params


def get_mlmmj_params_from_api(form):
    """Convert parameter names/values in API form to mlmmjadmin parameters.

    :param form: dict of web form.

    It also supports all parameters supported by mlmmjadmin.
    """
    # `kvs` stores mlmmj parameters
    kvs = form.copy()

    #
    # Get max message size (in bytes)
    #
    if 'max_message_size' in form:
        kvs['max_message_size'] = 0

        try:
            _size = abs(int(form.get('max_message_size', 0)))
            kvs['max_message_size'] = _size
        except:
            kvs.pop('max_message_size')

    return kvs
