# Copyright 2014 Facebook, Inc.

# You are hereby granted a non-exclusive, worldwide, royalty-free license to
# use, copy, modify, and distribute this software in source code or binary
# form for use in connection with the web services and APIs provided by
# Facebook.

# As with any software that integrates with the Facebook platform, your use
# of this software is subject to the Facebook Developer Principles and
# Policies [http://developers.facebook.com/policy/]. This copyright notice
# shall be included in all copies or substantial portions of the software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

from facebook_business.exceptions import FacebookBadObjectError

import hashlib
import six
import re

class CustomAudienceMixin:

    class Schema(object):
        uid = 'UID'
        email_hash = 'EMAIL_SHA256'
        phone_hash = 'PHONE_SHA256'
        mobile_advertiser_id = 'MOBILE_ADVERTISER_ID'

        class MultiKeySchema(object):
            extern_id = 'EXTERN_ID'
            email = 'EMAIL'
            phone = 'PHONE'
            gen = 'GEN'
            doby = 'DOBY'
            dobm = 'DOBM'
            dobd = 'DOBD'
            ln = 'LN'
            fn = 'FN'
            fi = 'FI'
            ct = 'CT'
            st = 'ST'
            zip = 'ZIP'
            madid = 'MADID'
            country = 'COUNTRY'
            appuid = 'APPUID'

    @classmethod
    def format_params(cls,
                      schema,
                      users,
                      is_raw=False,
                      app_ids=None,
                      pre_hashed=None,
                      session=None):
        hashed_users = []
        if schema in (cls.Schema.phone_hash,
                      cls.Schema.email_hash,
                      cls.Schema.mobile_advertiser_id):
            for user in users:
                if schema == cls.Schema.email_hash:
                    user = user.strip(" \t\r\n\0\x0B.").lower()
                if isinstance(user, six.text_type) and not(pre_hashed) and schema != cls.Schema.mobile_advertiser_id:
                    user = user.encode('utf8')  # required for hashlib
                # for mobile_advertiser_id, don't hash it
                if pre_hashed or schema == cls.Schema.mobile_advertiser_id:
                    hashed_users.append(user)
                else:
                    hashed_users.append(hashlib.sha256(user).hexdigest())
        elif isinstance(schema, list):
            # SDK will support only single PII
            if not is_raw:
                raise FacebookBadObjectError(
                    "Please send single PIIs i.e. is_raw should be true. " +
                    "The combining of the keys will be done internally.",
                )
            # users is array of array
            for user in users:
                if len(schema) != len(user):
                    raise FacebookBadObjectError(
                        "Number of keys in each list in the data should " +
                        "match the number of keys specified in scheme",
                    )
                    break

                # If the keys are already hashed then send as it is
                if pre_hashed:
                    hashed_users.append(user)
                else:
                    counter = 0
                    hashed_user = []
                    for key in user:
                        key = key.strip(" \t\r\n\0\x0B.").lower()
                        key = cls.normalize_key(schema[counter],
                                                           str(key))
                        if schema[counter] != \
                                cls.Schema.MultiKeySchema.extern_id:
                            if isinstance(key, six.text_type):
                                key = key.encode('utf8')
                            key = hashlib.sha256(key).hexdigest()
                        counter = counter + 1
                        hashed_user.append(key)
                    hashed_users.append(hashed_user)

        payload = {
            'schema': schema,
            'is_raw': is_raw,
            'data': hashed_users or users,
        }

        if schema == cls.Schema.uid:
            if not app_ids:
                raise FacebookBadObjectError(
                    "Custom Audiences with type " + cls.Schema.uid +
                    "require at least one app_id",
                )

        if app_ids:
            payload['app_ids'] = app_ids

        params = {
            'payload': payload,
        }
        if session:
            params['session'] = session
        return params

    @classmethod
    def normalize_key(cls, key_name, key_value=None):
        """
            Normalize the value based on the key
        """
        if key_value is None:
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.extern_id or
           key_name == cls.Schema.MultiKeySchema.email or
           key_name == cls.Schema.MultiKeySchema.madid or
           key_name == cls.Schema.MultiKeySchema.appuid):
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.phone):
            key_value = re.sub(r'[^0-9]', '', key_value)
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.gen):
            key_value = key_value.strip()[:1]
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.doby):
            key_value = re.sub(r'[^0-9]', '', key_value)
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.dobm or
           key_name == cls.Schema.MultiKeySchema.dobd):

            key_value = re.sub(r'[^0-9]', '', key_value)
            if len(key_value) == 1:
                key_value = '0' + key_value
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.ln or
           key_name == cls.Schema.MultiKeySchema.fn or
           key_name == cls.Schema.MultiKeySchema.ct or
           key_name == cls.Schema.MultiKeySchema.fi or
           key_name == cls.Schema.MultiKeySchema.st):
            key_value = re.sub(r'[^a-zA-Z]', '', key_value)
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.zip):
            key_value = re.split('-', key_value)[0]
            return key_value

        if(key_name == cls.Schema.MultiKeySchema.country):
            key_value = re.sub(r'[^a-zA-Z]', '', key_value)[:2]
            return key_value

    def add_users(self,
                  schema,
                  users,
                  is_raw=False,
                  app_ids=None,
                  pre_hashed=None,
                  session=None):
        """Adds users to this CustomAudience.

        Args:
            schema: A CustomAudience.Schema value specifying the type of values
                in the users list.
            users: A list of identities respecting the schema specified.

        Returns:
            The FacebookResponse object.
        """
        return self.get_api_assured().call(
            'POST',
            (self.get_id_assured(), 'users'),
            params=self.format_params(
                schema,
                users,
                is_raw,
                app_ids,
                pre_hashed,
                session,
            ),
        )

    def remove_users(self,
                     schema,
                     users,
                     is_raw=False,
                     app_ids=None,
                     pre_hashed=None,
                     session=None):
        """Deletes users from this CustomAudience.

        Args:
            schema: A CustomAudience.Schema value specifying the type of values
                in the users list.
            users: A list of identities respecting the schema specified.

        Returns:
            The FacebookResponse object.
        """
        return self.get_api_assured().call(
            'DELETE',
            (self.get_id_assured(), 'users'),
            params=self.format_params(
                schema,
                users,
                is_raw,
                app_ids,
                pre_hashed,
                session,
            ),
        )

    def share_audience(self, account_ids):
        """Shares this CustomAudience with the specified account_ids.

        Args:
            account_ids: A list of account ids.

        Returns:
            The FacebookResponse object.
        """
        return self.get_api_assured().call(
            'POST',
            (self.get_id_assured(), 'adaccounts'),
            params={'adaccounts': account_ids},
        )

    def unshare_audience(self, account_ids):
        """Unshares this CustomAudience with the specified account_ids.

        Args:
            account_ids: A list of account ids.

        Returns:
            The FacebookResponse object.
        """
        return self.get_api_assured().call(
            'DELETE',
            (self.get_id_assured(), 'adaccounts'),
            params={'adaccounts': account_ids},
        )
