# (c) cavaliba.com - data - field - password
# v3.28

from app_home.configuration import get_configuration
from .field import Field
from app_data import crypto

# -------------
# PASSWORD
# -------------

# multi supported: no
# dataformat_ext:
#   - mode:hash (default)
#   - mode:store (encrypted reversible storage)
#   - minlen:INT
#   - maxlen:INT
#   - complexity:INT   (must have character class)

class FieldPassword(Field):


    def __init__(self, fieldname, fieldschema, alljson):
        super().__init__(fieldname, fieldschema, alljson)

        self.mode = 'hash'
        self.minlen = 8
        self.maxlen = 64
        self.complexity = 3   # min complexity factors: lower/upper/digit/other
        self.len_seen = None
        self.complexity_seen = None


        if self.dataformat_ext:
            opts = self.dataformat_ext.split(' ')

            for opt in opts:

                #  mode store ?
                if opt == "store":
                    self.mode = 'store'

                elif opt.startswith("minlen:"):
                    try:
                        self.minlen = int(opt.split(":")[1])
                    except (ValueError, IndexError):
                        pass
                elif opt.startswith("maxlen:"):
                    try:
                        self.maxlen = int(opt.split(":")[1])
                    except (ValueError, IndexError):
                        pass
                elif opt.startswith("complexity:"):
                    try:
                        self.complexity = int(opt.split(":")[1])
                    except (ValueError, IndexError):
                        pass




    def get_datapoint_ui_detail(self):
        '''
        set to ********* if value is set
        '''

        datapoint = super().get_datapoint_ui_detail()
        datapoint["value"] = []
        if len(self.value) == 1:
            datapoint["value"].append('****************************')
        return datapoint


    def get_datapoint_ui_edit(self):

        datapoint = super().get_datapoint_ui_edit()
        datapoint["value"] = []
        if len(self.value) == 1:
            datapoint["value"].append('******************************')
        return datapoint



    def merge_request(self, request):
        
        # don't erase existing value (hashed/crypted)
        # self.value = []

        # data is a list [] in clear format
        #   empty  => no password provided
        #   *****  => keep previous password
        #   other  => hash or encrypt

        data = request.POST.getlist(self.fieldname, default=[])
        if isinstance(data, list):
            val = data[0]
            if val != '*' * len(val):
                if self.observe_strength(val):
                    obfuscated = self.obfuscate(val)
                    self.value = [obfuscated]
                # else:
                #   pass  # leave previous value if any


    # def merge_import(self, data):
    #    => default like regular string ; handle obfuscated data only


    def observe_strength(self, clear):

        self.len_seen = len(clear)

        self.complexity_seen = 0
        if any(c.islower() for c in clear):
            self.complexity_seen += 1
        if any(c.isupper() for c in clear):
            self.complexity_seen += 1
        if any(c.isdigit() for c in clear):
            self.complexity_seen += 1
        if any(not c.isalnum() for c in clear):
            self.complexity_seen += 1

        if self.len_seen < self.minlen:
            return False
        if self.len_seen > self.maxlen:
            return False
        if self.complexity_seen < self.complexity:
            return False

        return True

    def obfuscate(self, clear):

        if self.mode == 'hash':
            return crypto.hash_create(clear)
        else:
            key = get_configuration("env" ,"CAVALIBA_CIPHER_KEY")
            return crypto.string_encode(key, clear)



    def is_valid(self):
        r = super().is_valid()

        if self.len_seen:
            if self.len_seen < self.minlen:
                return False
            if self.len_seen > self.maxlen:
                return False
        if self.complexity_seen:
            if self.complexity_seen < self.complexity:
                return False

        return r
