# By: Riasat Ullah
# This file contains functions to validate strings.

from utils import constants
import configuration
import datetime
import re


foreign_letters = "àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽð"


def is_not_sql_injection(text):
    '''
    Checks if this string is an sql injection or not.
    :return: (boolean) True if it is, False otherwise
    '''
    keywords = {'insert': 'into', 'delete': 'from', 'drop': 'table'}
    split_text = text.split()
    if len(split_text) > 1:
        for i in range(0, len(split_text) - 1):
            if split_text[i] in keywords:
                word = split_text[i]
                if split_text[i + 1] == keywords[word]:
                    return False
    return True


def has_punctuations(text):
    '''
    Checks if a string has punctuation or not.
    :return: (boolean) True if it does, False otherwise
    '''
    pattern = r'''.*[\!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\\\].*'''
    match = re.search(pattern, text)
    if match:
        return True
    else:
        return False


def is_valid_password(text):
    '''
    Checks if a password is valid or not. A valid password
    must meet the following requirements:
        1. Must be at least 6 characters long
        2. Must not contain spaces
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = r'^[\S]{6,}$'
    if re.match(pattern, text):
        return True
    return False


def is_only_letters(text):
    '''
    Checks if a string has only letters or not.
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = '^[a-zA-Z\u00C0-\u00FF]+$'
    if re.match(pattern, text):
        return True
    else:
        return False


def is_number(text):
    '''
    Checks if a string is a number or not.
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = r'^\d+$'
    if re.match(pattern, text):
        return True
    else:
        return False


def is_date(text):
    '''
    Checks if the string is a valid date or not.
    All dates should be in these formats:
        1. YYYYMMDD
        2. YYYY-MM-DD
        3. YYYY/MM/DD
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = '^[0-9]{4}[-/]{0,1}[0-9]{2}[-/]{0,1}[0-9]{2}$'
    hyphen = '-'
    backslash = '/'
    if re.match(pattern, text):
        date_text = text
        if hyphen in date_text:
            date_text = date_text.replace(hyphen, '')
        elif backslash in date_text:
            date_text = date_text.replace(backslash, '')
        if len(date_text) == 8:
            try:
                datetime.datetime.strptime(date_text, '%Y%m%d')
                return True
            except ValueError:
                return False
    return False


def is_timestamp(text):
    '''
    Checks if the string is a valid timestamp or not.
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = '^[0-9]{4}[-/]{0,1}[0-9]{2}[-/]{0,1}[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$'
    if re.match(pattern, text):
        return True
    return False


def is_timestamp_with_milliseconds(text):
    '''
    Checks if the string is a valid json timestamp with milliseconds or not.
    '''
    pattern_milli = '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]+$'
    if re.match(pattern_milli, text):
        return True
    return False


def is_json_timestamp(text):
    '''
    Checks if the string is a valid json timestamp or not.
    '''
    pattern = '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$'
    if re.match(pattern, text):
        return True
    return False


def is_json_timestamp_with_milliseconds(text):
    '''
    Checks if the string is a valid json timestamp with milliseconds or not.
    '''
    pattern_milli = '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]+$'
    if re.match(pattern_milli, text):
        return True
    return False


def is_json_timestamp_with_milliseconds_and_timezone(text):
    '''
    Checks if the string is a valid json timestamp with milliseconds and timezone or not.
    '''
    pattern_milli_tz = '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]+Z$'
    if re.match(pattern_milli_tz, text):
        return True
    return False


def is_phone_number(text):
    '''
    Checks if the string is a valid phone number or not
    :return: (boolean) True is it is; False otherwise
    '''
    pattern = r'^\+{0,1}[0-9]+$'
    phone_text = text.replace('-', '').replace(' ', '')
    if re.match(pattern, phone_text):
        return True
    else:
        return False


def is_empty_string(text):
    '''
    Checks if a string is empty or not.
    '''
    pattern = r"^\s*$"
    if re.match(pattern, text):
        return True
    return False


def is_standard_name(text):
    '''
    Checks if a string is a standard name or not.
    '''
    # pattern = r"^[0-9a-zA&-ZàáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽð ,.'\-()\/\\]+$"
    if not is_empty_string(text):
        return True
    return False


def is_valid_preferred_username(text):
    '''
    Checks if the string is a valid username or not.
    A username must be 5 characters long be alphanumeric, but can contain (-  _ .) in the middle
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = r"^([0-9a-z\u00C0-\u00FF]+([-_\.]{0,1}[0-9a-z\u00C0-\u00FF]*)*){5,}$"
    if len(text.split()) == 1 and re.match(pattern, text):
        return True
    return False


def is_email_address(text):
    '''
    Checks if the string is a valid email address or not
    :return: (boolean) True if it is; False otherwise
    '''
    pattern = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
    if re.match(pattern, text):
        return True
    return False


def is_taskcall_email_address(text):
    '''
    Checks if an email address is a valid taskcall email address or not.
    :return: (boolean) True if it is; False otherwise
    '''
    if is_email_address(text):
        tail = text.split('@')[1].split('.')
        if is_valid_subdomain(tail[0]) and\
                '.'.join([tail[1], tail[2]]) in [constants.domain_europe, constants.domain_us]:
            return True
    return False


def is_valid_ip_address(text):
    '''
    Checks if the string is an ip address or not.
    :return: True if it is a valid ip address; False otherwise
    '''
    split_address = text.split('.')
    if len(split_address) != 4:
        return False
    else:
        for item in split_address:
            try:
                # we are using assertion in case the text is not convertible to int
                assert 0 <= int(item) <= 255
            except AssertionError:
                return False
    return True


def is_valid_subdomain(text):
    '''
    Checks if a string is a valid subdomain or not.
    :return: True if is; False otherwise
    '''
    pattern_alphanumeric = '^[a-z0-9]+([-]{1}[a-z0-9]+)*$'
    reserved_words = ['api', 'app', 'us', 'eu', 'integrations', 'incidents', 'incidents-api', 'taskcall', 'taskcallapp',
                      'check', 'checks', 'status', 'statuses', 'issues']
    try:
        # 1. make sure there are no white spaces
        # 2. make sure that it is alphanumeric
        # 3. make sure that it is not one of the reserved words
        text = text.lower()
        assert ' ' not in text
        assert re.match(pattern_alphanumeric, text)
        assert text not in reserved_words
        return True
    except AssertionError:
        return False


def is_phone_country_code(text):
    '''
    Checks if a string is a numeric phone country code or not.
    '''
    pattern = '^[0-9]{1,3}$'
    if re.match(pattern, text):
        return True
    return False


def is_last_four_card_number(text):
    '''
    Checks if string is last four digits of a card number or not.
    '''
    pattern = '^[0-9]{4}$'
    if re.match(pattern, text):
        return True
    return False


def is_postmortem_report_number(text):
    '''
    Checks if a string is a postmortem report reference ID or not.
    '''
    pattern = '^[A-Z0-9]{' + str(configuration.postmortem_report_reference_id_length) + '}$'
    if re.match(pattern, text):
        return True
    return False


def is_zip_code(text):
    '''
    Checks if a string is a valid zip code or not.
    '''
    # pattern = '^[A-Z0-9 -]{3,15}$'
    # if re.match(pattern, text):
    #     return True
    # return False
    return True


def is_telephone_response_code(text):
    '''
    Checks if a string is valid sms response or not.
    '''
    pattern = '^[0-9]{1,4}$'
    if re.match(pattern, text):
        return True
    return False


def is_jwt_token(text):
    '''
    Checks if a string is a JWT token or not.
    '''
    pattern = r'^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$'
    if re.match(pattern, text):
        return True
    return False


def is_api_key(text):
    '''
    Checks if a string is an API key or not.
    '''
    pattern_europe = '^[a-zA-Z0-9]{' + str(configuration.api_key_length[constants.aws_europe_paris]) + '}$'
    pattern_us = '^[a-zA-Z0-9]{' + str(configuration.api_key_length[constants.aws_us_ohio]) + '}$'
    if re.match(pattern_europe, text) or re.match(pattern_us, text):
        return True
    return False


def is_status_dashboard_url_extension(text):
    '''
    Checks if a string is in the acceptable status dashboard url extension format or not.
    '''
    pattern = '^[a-z0-9]+([-]{1}[a-z0-9]+)*$'
    if re.match(pattern, text):
        return True
    return False


def is_web_url(text):
    '''
    Checks if a string is a valid web url or not.
    '''
    pattern = r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+%,;=.]+$"
    if re.match(pattern, text):
        return True
    return False


def is_dial_in_number(text):
    '''
    Checks if a string is a valid dial in number or not.
    '''
    pattern = r'^\+{0,1}[0-9-]+([,]+[0-9]+#)*$'
    if re.match(pattern, text):
        return True
    return False


def is_valid_uuid(text):
    '''
    Checks if a string is a valid UUID or not.
    '''
    pattern = "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
    if re.match(pattern, text):
        return True
    return False


def is_file_path(text):
    '''
    Checks if a string is a valid file path or not.
    '''
    pattern = r"^(\S+)\/([^\/][\S]+)$"
    if re.match(pattern, text):
        return True
    return False


def get_instance_id_from_blacklist_reason(text):
    '''
    Tries to get instance ID from the reason stored for a blacklisted event.
    :return: (int) Instance ID  |  None if a match is not captured
    '''
    pattern = r"^.*(?i)instance\sid\s-\s([0-9]+):.*$"
    found = re.search(pattern, text)
    if found is not None:
        try:
            return int(found.group(1))
        except Exception:
            return None
    else:
        return None


def is_literal_list(text):
    '''
    Checks if a string could be a list object.
    '''
    pattern = r"^\[.*\]$"
    if re.match(pattern, text):
        return True
    return False


def is_hex_color(text):
    '''
    Check if a string is a hex color or color.
    '''
    pattern = "^#(?:[0-9a-fA-F]{3}){1,2}$"
    if re.match(pattern, text):
        return True
    return False
