import os
import re
import socket
import sys

_HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(_HERE, "..", "..", "import"))
sys.path.insert(0, os.path.join(_HERE, "..", "import", "scripts"))
from exp_file_filter_import import ExpFileFilterImporter

from logging_handler import LoggerHandler
from em_encryption_handler import EmEncryptionManager
from pbkdf2_encryption_handler import PBKDF2Encryption

em_home = os.environ.get("EM_HOME", "")


class QualificationsValidator:
    """
    class for qualifications validation before running export script
    """

    def __init__(self,skip_verify_ctm_os):
        self.comm_table = ExpFileFilterImporter._table_to_rows('COMM')
        self.params_table = ExpFileFilterImporter._table_to_rows('PARAMS')


        if not skip_verify_ctm_os:
            self.servers_os = self.init_servers_os()
        self.servers_with_ha = self.init_servers_ha()
        self.min_ctm_version = '92130'
        self.valid_server_name_regex = r'^[a-zA-Z0-9](\w|-){1,20}$'
        self.invalid_server_name_regex = r'^IN\w{2}$'
        self.server_id_regex = r'^(H|h)[01-99]{2}$'
        self.unsupported_os_21300 = ['Microsoft Windows', 'AIX']
        self.unsupported_os = ['AIX']
        self.orig_ctm_key_content_hash = '!PBKDF2!F65C004BCB279F7A10B8B03C284FBAE44A6D378B9092F4E70A3ADCB8E3459049779506B1DB81AF16AAACFC37A9CB66B3140675A9F711C10EC1218845BB6D14E24FB26A0A354099FF4E392C88023A8131B87C1C56707628A0B1678853039BDF7BF32B6514697B855A09FB57DC3A7782C1E16172341BAFFA0261B28942AD814654'

    def get_db_table(self, table_name):
        return ExpFileFilterImporter._table_to_rows(table_name.upper())

    def init_servers_os(self):
        """
        :return: dictionary of mapping IP addresses of the servers in the environment to their operating system
        for example:
        {'10.63.38.102': 'Red Hat Enterprise Linux Server release', '10.63.34.222': 'Red Hat Enterprise Linux Server release', '10.63.55.234': 'Microsoft Windows'}
        """
        ip_os_mapping = {}
        mng_servers = self.get_db_table('mng_servers')
        for entry in mng_servers:
            if not (entry.get('os_type') == "z/OS"):
                hostname = entry.get('hostname')
                ip = self.get_host_ip(hostname)
                if ip is not None:
                    ip_os_mapping[ip] = entry.get('os_type')
        LoggerHandler.print_message(f"Mapping of IP addresses to their operating system:\n{ip_os_mapping}",
                                    logger_level='debug')
        return ip_os_mapping

    def get_host_ip(self, hostname):
        """
        :return: IP address of host or None in a case of error
        """
        try:
            if '.' in hostname:  # if it's FQDN
                hostname = hostname.split('.')[0]
            ip = socket.gethostbyname(hostname)
            LoggerHandler.print_message(f"IP address of {hostname} is: {ip}", logger_level='debug')
            return ip
        except Exception as e:
            LoggerHandler.print_message(f"Could not get IP address of {hostname}: {str(e)}", logger_level='debug')
            return None

    def init_servers_ha(self):
        servers_with_ha = []
        ctm_config_ha = self.get_db_table('ctm_config_ha')
        for entry in ctm_config_ha:
            if entry.get('has_ha') != 'Off':
                servers_with_ha.append(entry.get('data_center'))
        return servers_with_ha

    def qualifications_validate(self, skip_cjk_check, skip_verify_min_ctm_version, skip_verify_ctm_os, skip_verify_ctm_ha, em_version, validate_servers):
        """
        Run the following qualification tests:
        1.  Minimum CTM version is 9.0.21.300
        2.  Server name (up to 20 chars, no special chars  and not INxx)
        3.  Server id is not H01-H99 and h01-h99
        4.  ctm_key.txt was not changed by the customer
        5.  Unsupported Control-M Servers OS
        6.  Server is not installed with HA
        7.  EM is not installed with cjk
        8.  EM version is higher than 9.0.20.000

        :return: True if all the qualification tests passed successfully
        """
        try:
            LoggerHandler.print_message("Starting qualifications validation")
            return self.verify_servers(skip_verify_min_ctm_version, skip_verify_ctm_os, skip_verify_ctm_ha, validate_servers) \
                and self.verify_ctm_key() \
                and self.verify_none_cjk_encoding(skip_cjk_check) \
                and self.verify_em_version(em_version)
        except Exception as e:
            raise Exception(f"Qualification validation has failed: {str(e)}")

    def verify_em_version(self, em_version):
        """
        :return: return True if EM version is higher or equal to 9.0.20.000
        """
        min_version = "9.0.20.000"
        if self.compare_versions(em_version, min_version) < 0:
             LoggerHandler.print_message(
                message=f"The EM version [{em_version}] is lower than 9.0.20.000 and cannot be migrated to Control-M SaaS.",
                screen_print=True,
                logger_level='error')
             return False
        LoggerHandler.print_message(f"### TEST 9 PASSED ### EM version [{em_version}] is valid for Control-M SaaS")
        return True

    def compare_versions(self, version1, version2):
        v1_parts = [int(x) for x in version1.split('.')]
        v2_parts = [int(x) for x in version2.split('.')]
        if v1_parts > v2_parts:
            return 1
        elif v1_parts < v2_parts:
            return -1
        return 0

    def verify_servers(self, skip_verify_min_ctm_version, skip_verify_ctm_os, skip_verify_ctm_ha, validate_servers):
        if validate_servers == 'none':
            return True
        valid = True
        for entry in self.comm_table:
            is_hybrid_server='yes'
            if validate_servers == 'ask':
                is_hybrid_server = input(f"Is {entry.get('data_center')} going to remain SelfHosted? (yes/no): ").lower()
            if is_hybrid_server in ('yes', 'y') or validate_servers == 'all':
                server_valid = self.verify_min_ctm_version(entry.get('data_center'), entry.get('control_m_ver'),
                                            entry.get('ctm_fp_level'), skip_verify_min_ctm_version) and \
                self.verify_server_name(entry.get('data_center')) and \
                self.verify_server_id(entry.get('data_center'), entry.get('code')) and \
                self.verify_ctm_os(entry.get('data_center'), entry.get('ctm_host_name'), entry.get('platform'), entry.get('control_m_ver'), skip_verify_ctm_os) and \
                self.verify_ctm_ha(entry.get('data_center'), skip_verify_ctm_ha)
                valid = valid and server_valid
        return valid

    def verify_min_ctm_version(self, server_name, major_version, fp_version, skip):
        """
        :return: return True if Server version is 9.0.21.300 at least
        """
        if skip:
            return True
        if fp_version is None:
            fp_version = ""
        ctm_version = major_version + fp_version
        if ctm_version < self.min_ctm_version:
            LoggerHandler.print_message(
                message=f"The server version [{ctm_version}] for [{server_name}] cannot be migrated to Control-M SaaS.",
                screen_print=True,
                logger_level='error')
            return False
        LoggerHandler.print_message(
            f"### TEST 1 PASSED ### Server version [{ctm_version}] for [{server_name}] is valid for Control-M SaaS")
        return True

    def verify_server_name(self, server_name):
        """
        :return: return True if Server name is up to 20 chars, without special chars and is not INxx
        """
        if not re.match(self.valid_server_name_regex, server_name) or re.match(self.invalid_server_name_regex,
                                                                               server_name):
            LoggerHandler.print_message(message=f"The server name [{server_name}] cannot be migrated to Control-M SaaS - Server name is not valid, it must be up to 20 chars, without special chars and is not INxx.",
                                        screen_print=True,
                                        logger_level='error')
            return False
        LoggerHandler.print_message(f"### TEST 2 PASSED ### Server name [{server_name}] is valid for Control-M SaaS")
        return True

    def verify_server_id(self, server_name, server_id):
        """
        :return: return True if Server id is not H01-H99 and h01-h99
        """
        if re.match(self.server_id_regex, server_id):
            LoggerHandler.print_message(
                message=f"The server id [{server_id}] for [{server_name}] cannot be migrated to Control-M SaaS - Server id is not valid, it must be not H01-H99 and h01-h99.",
                screen_print=True,
                logger_level='error')
            return False
        LoggerHandler.print_message(
            f"### TEST 3 PASSED ### Server id [{server_id}] for [{server_name}] is valid for Control-M SaaS")
        return True

    def verify_ctm_os(self, server_name, server_hostname, server_platform, major_version, skip):
        """
        :return: return True if Server operating system is supported for the export
        """
        if skip:
            return True
        if server_platform == "MVS":
            os_type = "MVS"
            LoggerHandler.print_message(
                f"### TEST 4 PASSED ### Server operating system {os_type} for {server_name} is valid for Control-M SaaS")
            return True
        server_ip = self.get_host_ip(server_hostname)
        if server_ip is not None and server_ip in self.servers_os:
            os_type = self.servers_os[server_ip].strip()
            if major_version >= '922':
                if any(os_type.lower() in os.lower() for os in self.unsupported_os):
                    LoggerHandler.print_message(
                        message=f"The server operating system [{os_type}] for [{server_name}] running on version [{major_version}] cannot be connected as SelfHosted to Control-M SaaS.",
                        screen_print=True,
                        logger_level='error')
                    return False
            else:
                if any(os_type.lower() in os.lower() for os in self.unsupported_os_21300):
                    LoggerHandler.print_message(
                        message=f"The server operating system [{os_type}] for [{server_name}] running on version [{major_version}] cannot be connected as SelfHosted to Control-M SaaS.",
                        screen_print=True,
                        logger_level='error')
                    return False
            LoggerHandler.print_message(
                f"### TEST 4 PASSED ### Server operating system [{os_type}] for [{server_name}] is valid for Control-M SaaS")
        else:
            # if the server has no entry in mng_servers table, we cannot find its operating system - ignoring the test and not failing the export
            LoggerHandler.print_message(
                f"### TEST 4 IGNORED ### Server operating system for [{server_name}] could not be found",
                logger_level='warning')
        return True

    def verify_ctm_ha(self, server_name, skip):
        """
        :return: return True if Server is not installed with HA
        """
        if skip:
            return True
        if server_name in self.servers_with_ha:
            LoggerHandler.print_message(
                message=f"The server [{server_name}] is installed with HA and cannot be migrated to Control-M SaaS.",
                screen_print=True,
                logger_level='error')
            return False
        LoggerHandler.print_message(
            f"### TEST 5 PASSED ### Server [{server_name}] is not installed with HA and is valid for Control-M SaaS")
        return True

    def verify_ctm_key(self):
        """
        This method compares the content of ctm_key.txt file on the customer environment against the original key
        :return: True if ctm_key.txt was not changed by the customer
        """
        ctm_key_path = os.path.join(str(em_home), 'ini', 'fips', 'ctm_key.txt')
        ctm_key_content = EmEncryptionManager.read_key_file(ctm_key_path)
        ctm_key_content_hash = PBKDF2Encryption.hash_string(ctm_key_content) if ctm_key_content else None
        if ctm_key_content_hash and ctm_key_content_hash == self.orig_ctm_key_content_hash:
            LoggerHandler.print_message(f"### TEST 6 PASSED ### ctm_key.txt was not changed by the customer")
            return True
        LoggerHandler.print_message(message=f"The encryption key was changed.",
                                    screen_print=True,
                                    logger_level='error')
        return False

    def verify_none_cjk_encoding(self, skip_cjk_check):
        """
        This method is to verify the em is not encoded with ckj
        In case the encoding is ckj the method will return False, and True otherwise
        """
        if skip_cjk_check:
            LoggerHandler.print_message(
                message=f"Skipping test for cjk System configuration.",
                screen_print=True,
                logger_level='info')
            return True
        for item in self.params_table:
            if item.get('pname') == 'I18N' and item.get('pvalue') == "cjk":
                LoggerHandler.print_message(
                    message=f"The system is configured for CJK languages and cannot be migrated to Control-M SaaS.",
                    screen_print=True,
                    logger_level='error')
                return False

        LoggerHandler.print_message(f"### TEST 7 PASSED ### The system is not configured for CJK languages")
        return True
