# Before running the Python script make the following changes in the code
# 1. Set the file path of TestConnection.exe. (Recommended: Runner folder)
# 2. Set the file path of connections.config.
# 3. This script should be run from a different account than the one being used by ActivityRunner Service. This account will need modify access to write to connections.config. Do not provide modify access to the config files to the account being used by ActivityRunner Service.
# 4. Set the file path of connectionOptions.xml from which we will take the fallback connections string. (Recommended: Runner folder)

# Logic:
# 1. This assumes the existence of two environments - Production (primary) and DR (secondary).
# 2. In connectionOptions.xml, "primary" has to contain all the Production strings unaltered.
# 3. The script compares connection strings in connections.config with connection strings in primary and secondary. The name must exist in both sections and one of them should match the connection in connections.config. If neither of them matches, an error is produced and the connection with that name is ignored.
# 4. The script checks if the valid connections in connections.config are reachable. If not, then it switches them from primary to secondary and vice-versa.

import xml.etree.ElementTree as ET
import subprocess
import argparse

class Args(argparse.Namespace):
    config: str
    options: str
    executable: str
    tries: int

def parse_args() -> Args:
    parser = argparse.ArgumentParser(description='Manage database connections.')
    parser.add_argument('--config', default='connections.config', help='Path to the connections config file.')
    parser.add_argument('--options', default='connectionOptions.xml', help='Path to the connection options XML file.')
    parser.add_argument('--executable', default='TestConnection.exe', help='Path to the connection test executable.')
    parser.add_argument('--tries', type=int, default=5, help='Number of times to attempt a connection.')
    return parser.parse_args()

class Connection:
    def __init__(self, element: ET.Element):
        self.element = element
        self.name = element.get('name')
        self.cstring = element.get('connectionString')
        self.provider = element.get('providerName')

	def copy_from(self, c: 'Connection'):
		self.element.set('name', c.name)
		self.element.set('connectionString', c.cstring)
		if c.provider is not None:
            self.element.set("providerName", c.provider)
		else if self.provider is not None:
			self.element.attrib.pop("providerName")
		self.name = c.name
		self.cstring = c.cstring
		self.provider = c.provider

    executable_path: str
    tries: str

    @staticmethod
    def setup(executable_path: str, tries: str):
        Connection.executable_path = executable_path
        Connection.tries = tries

    def equals(self, c: 'Connection'):
        return ((
            self.name == c.name
        ) and (
            self.cstring == c.cstring
        ) and (
            self.provider == c.provider))

    def test(self):
        argument = ["string", self.cstring]
        if self.provider is not None:
            argument.append(self.provider)
        for _ in range(Connection.tries):
            try:
                exe_output = subprocess.run(
                    [Connection.executable_path] + argument, text=True, capture_output=True, check=False)
                if exe_output.stdout == 'Connection successful\n':
                    return True
            except subprocess.CalledProcessError as e:
                print("TestConnection.exe produced an error: ", e, file=sys.stderr)
        return False

def load_xml_file(file_path):
    try:
        return ET.parse(file_path)
    except ET.ParseError as e:
        raise Exception(f"Error parsing XML file {file_path}: {e}")

def get_valid_connection_names(config_dict: dict[str, ET.Element],
                            main_dict: dict[str, ET.Element],
                            fallback_dict: dict[str, ET.Element]):
	valid_conns = []
    for config_child in config_dict.values():
        config_conn = Connection(config_child)
        main_conn = Connection(main_dict[config_conn.name]) if config_conn.name in main_dict else None
        fallback_conn = Connection(fallback_dict[config_conn.name]) if config_conn.name in fallback_dict else None
		if main_conn is None and fallback_conn is not None:
			print(f"The name {config_conn.name} exists in secondary but not in primary.", file=sys.stderr)
			continue
		if main_conn is not None and fallback_conn is None:
			print(f"The name {config_conn.name} exists in primary but not in secondary.", file=sys.stderr)
			continue
		if main_conn is None:
			continue
        if ((not config_conn.equals(main_conn)) and (
            not config_conn.equals(fallback_conn))):
            print(f"The connection string for name {config_conn.name} matches neither primary nor secondary.", file=sys.stderr)
			continue
		valid_conns.append(config_conn.name)
	return valid_conns	

def main(args: Args):
    Connection.setup(args.executable, args.tries)
    should_write_to_config = False
    config_file = load_xml_file(config_path)
    options_file = load_xml_file(options_path)
    config_dict = { x.get('name'): x for x in config_file.getroot().findall('add') }
    main_dict = { x.get('name'): x for x in options_file.getroot().findall('./primary/add') }
    fallback_dict = { x.get('name'): x for x in options_file.getroot().findall('./secondary/add') }
    valid_conns = get_valid_connection_names(config_dict, main_dict, fallback_dict)
	for name in valid_conns:
		main_conn = Connection(main_dict[name])         
		fallback_conn = Connection(fallback_dict[name])
		current_conn = Connection(config_dict[name])
		if current_conn.test(): 
			continue
		if current_conn.equals(main_conn):
			if fallback_conn.test():
				current_conn.copy_from(fallback_conn)
				print(f"Connection string for {name} set to secondary.")
				should_write_to_config = True 
			else:
				print(f"Both connections failed for name {name}", file=sys.stderr)
		else:
			if main_conn.test():
				current_conn.copy_from(main_conn)
				print(f"Connection string for {name} set to primary.")
				should_write_to_config = True
			else:
				print(f"Both connections failed for name {name}", file=sys.stderr)
	if should_write_to_config is True:
		config_file.write(args.config, encoding='utf-8', xml_declaration=True)

if __name__ == "__main__":
    args = parse_args()
    main(args)