import traceback
import copy
from scripts.config.app_constants import *
from scripts.logging.logger import logger


class FlowConfigBuilder:
    def __init__(self, flow_json, device_info):
        self.source = []
        self.pipes = []
        self.flow_json = flow_json
        self.default_data = {
            "service_name": "pipeline_v2",
            "server_cat": None,
            "thread": 1,
            "error": {
                "path": "Error/"
            },
            "log": {
                "file_name": "ilens",
                "path": "logs/",
                "level": "DEBUG",
                "handler": "rotating_file_handler",
                "max_bytes": 10000000,
                "back_up_count": 10
            },
            "source": {},
            "pipe_blocks": {}
        }

        self.device_info = device_info

    def get_node_data(self, node_id):
        return self.flow_json["nodes"][node_id]

    def set_pipes(self, **kwargs):
        try:
            override_config = kwargs.get('override_config', False)
            pipeline_override_config = kwargs.get('pipeline_override_config', dict())

            pipe_blocks = dict()
            for each_pipe_node in self.pipes:
                node_data = self.get_node_data(each_pipe_node)

                if node_data["folder_name"] not in pipe_blocks:
                    pipe_blocks[node_data["folder_name"]] = []
                if override_config:
                    temp = pipeline_override_config.get(node_data['node_playground_id'], None)
                    if not temp:
                        logger.warning(f"Node with ID {node_data['node_playground_id']} "
                                       f"is not found in the UI override configuration")
                        continue
                else:
                    temp = copy.deepcopy(node_data["node_configuration"])

                temp["type"] = node_data["node_unique_id"]
                temp['node_component_type'] = node_data.get('node_component_type', '')
                temp['node_id'] = node_data['node_playground_id']

                if not override_config:
                    if "node_form_data" in node_data:
                        if "bodyContent" in node_data['node_form_data']:
                            temp.update(node_data['node_form_data']['bodyContent'])

                    if "node_configuration" in node_data:
                        node_configurations = node_data['node_configuration']
                        temp.update(node_configurations)

                if temp not in pipe_blocks[node_data["folder_name"]]:
                    pipe_blocks[node_data["folder_name"]].append(temp)

            return pipe_blocks
        except Exception as e:
            traceback.print_exc()
            return None

    def set_source(self):
        try:
            temp = dict()
            for each_source_node in self.source:
                node_data = self.get_node_data(each_source_node)
                temp[node_data["folder_name"]] = dict()
                temp[node_data["folder_name"]] = node_data["node_configuration"]
                temp[node_data["folder_name"]]["type"] = node_data["node_unique_id"]
            return temp
        except Exception as e:
            traceback.print_exc()
            return None

    def create_config(self, pipeline_id, job_id, pipeline_type, component_types, thread=1, **kwargs):
        try:
            connection_data = self.flow_json["connections"]
            node_data = self.flow_json["nodes"]
            node_list = []

            if len(connection_data):
                for each_link in connection_data:
                    source = str(each_link["sourceId"]).split("@")[0]
                    node_list.append(source)
                node_list.append(str(each_link["targetId"]).split("@")[0])

            for each_node in node_list:
                if node_data[each_node]["node_type"] in ["consumer"]:
                    self.source.append(each_node)
                    if each_node not in self.pipes:
                        self.pipes.append(each_node)
                else:
                    self.pipes.append(each_node)

            for each_component in node_data:
                if each_component not in self.pipes:
                    self.pipes.append(each_component)

            self.default_data["source"] = self.set_source()
            self.default_data["pipe_blocks"] = self.set_pipes(**kwargs)
            self.default_data['connections'] = self.get_connections()
            self.default_data["thread"] = thread
            topic_mapping, event_consumer = ParsePipelineMeta(pipeline_id=pipeline_id, job_id=job_id,
                                                              connections=self.default_data['connections'],
                                                              config_data=self.default_data,
                                                              component_types=component_types,
                                                              pipeline_type=pipeline_type,
                                                              device_info=self.device_info).identify_dependencies()

            self.default_data['topic_mapping'] = topic_mapping
            if event_consumer is not None:
                self.default_data['event_consumer'] = event_consumer

            return self.default_data

        except Exception as e:
            logger.error("Error occurred while creating the deployment configuration for the pipeline : {}".
                         format(str(e)), exc_info=True)
            raise Exception("Failed to create the deployment configuration for the pipeline")

    def get_connections(self):
        connection_data = self.flow_json['connections']
        if len(connection_data):
            for each in connection_data:
                each['sourceId'] = each['sourceId'].split('@')[0]
                each['targetId'] = each['targetId'].split('@')[0]
        return connection_data

    def get_commands(self, **kwargs):
        """
        {
  "type": "execute/form/None",
  "execute": [],
  "form": {
    "command": "",
    "args": [
      {
        "key": "",
        "value": "",
        "type": "short/long"
      }
    ],
    "executables": []
  }
}

        """
        nodes = self.flow_json.get('nodes', dict())
        node_unique_id = kwargs.get('node_unique_id', None)
        image_name = kwargs.get('image_name', None)
        for node in nodes:
            executor = nodes[node].get('base_executor', 'python').lower()
            node_id = nodes[node].get('node_playground_id', None)
            if node_id == node_unique_id:
                if executor == 'python':
                    return None
                elif executor == 'pyspark':
                    node_config = nodes[node].get('node_configuration', dict())
                    pyspark_config = node_config.get('pySparkConfig', dict())
                    args_list = [dict(key='deploy-mode', value=f"cluster", type='long'),
                                 dict(key='master', value=f"k8s://__k8s_url__", type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.namespace=__namespace__", type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.container.image={image_name}", type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.driver.pod.name=__pod_name__", type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.driver.label.pipeline_id=__pipeline_id__",
                                      type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.executor.label.pipeline_id=__pipeline_id__",
                                      type='long'),
                                 dict(key='conf',
                                      value=f"spark.kubernetes.driver.label.pipeline_version=__pipeline_version__",
                                      type='long'),
                                 dict(key='conf',
                                      value=f"spark.kubernetes.executor.label.pipeline_version=__pipeline_version__",
                                      type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.driver.label.node_id=__node_id__",
                                      type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.executor.label.node_id=__node_id__",
                                      type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.driver.label.device_id=__device_id__",
                                      type='long'),
                                 dict(key='conf', value=f"spark.kubernetes.executor.label.device_id=__device_id__",
                                      type='long')]
                    for conf in pyspark_config:
                        args_list.append(dict(key='conf',
                                              value=f"{conf.replace('_', '.')}={pyspark_config[conf]}",
                                              type='long'))

                    metadata = dict(type='form',
                                    form=dict(command='spark-submit',
                                              executables=['data-pipeline.py'],
                                              args=args_list))
                    return metadata
                else:
                    logger.warning(f'Unknown component base executor "{executor}"')
                    continue
            else:
                continue


class ParsePipelineMeta(object):
    def __init__(self, pipeline_id, job_id, connections, config_data, pipeline_type, component_types, device_info):
        try:
            self.pipeline_id = pipeline_id
            self.pipeline_type = pipeline_type
            self.component_types = component_types
            self.connections = connections
            self.config = config_data
            self.version = str(job_id)
            self.device_info = device_info

        except Exception as e:
            logger.exception(str(e))
            traceback.print_exc()

    def identify_dependencies(self):
        """
        This method is to identify the dependencies
        """
        dependency_json = dict()
        topic_mapping_json = dict()
        try:
            for each_item in self.connections:
                if each_item['sourceId'] not in dependency_json:
                    dependency_json[each_item['sourceId']] = [each_item['targetId']]
                else:
                    dependency_json[each_item['sourceId']].append(each_item['targetId'])
            writer_components = []

            if "Writer" in self.config['pipe_blocks']:
                for each in self.config['pipe_blocks']['Writer']:
                    writer_components.append(each['node_id'])

            for item in dependency_json:
                output_topic = self.pipeline_id + "/" + self.version + "/" + item + "_output"
                topic_mapping_json[item] = dict()

                if len(dependency_json[item]):
                    topic_mapping_json[item] = []
                    for each in dependency_json[item]:
                        tmp = {'input_topic': [output_topic], "component": each}
                        if each in writer_components:
                            tmp['output_topic'] = [each['topic'] for each in self.config['pipe_blocks']['Writer'] if
                                                   "Writer" in self.config['pipe_blocks']]
                        else:
                            tmp['output_topic'] = [self.pipeline_id + "/" + self.version + "/" + each + "_output"]
                        topic_mapping_json[item].append(tmp)
            result, event_consumer = self.group_dependencies(topic_mapping_json)
            return result, event_consumer
        except Exception as ae:
            traceback.print_exc()
            logger.error("Error : {}".format(str(ae)))

    def group_dependencies(self, data):
        event_consumer = None
        final_json = dict()
        event_consumer = self.identify_event_consumer()
        if event_consumer is not None:
            if event_consumer in self.component_types:
                if self.component_types[event_consumer].lower() == 'input':
                    final_json[event_consumer] = {
                        'output_topic': self.pipeline_id + "/" + self.version + "/" + event_consumer + '_output',
                        "component": event_consumer}
                else:
                    event_consumer_configurations = self.fetch_event_consumer_configurations(event_consumer)
                    final_json[event_consumer] = {'input_topic': event_consumer_configurations.get("topic", None),
                                                  'output_topic': self.pipeline_id + "/" + self.version + "/" + event_consumer + '_output',
                                                  "component": event_consumer}
                for item in data:
                    if len(data[item]):
                        for each in data[item]:
                            if each['component'] not in final_json:
                                final_json[each['component']] = each
                            else:
                                final_json[each['component']]['input_topic'] += each['input_topic']
        return final_json, event_consumer

    def identify_event_consumer(self):
        """
        This method is to identify the event consumer for the pipeline
        """
        try:
            pipeline_event_consumer = None
            logger.info("Identify event consumer for the pipeline")
            source_nodes_list = [source_node['sourceId'] for source_node in self.connections]
            target_nodes_list = [target_node['targetId'] for target_node in self.connections]
            for each_node in source_nodes_list:
                if each_node not in target_nodes_list:
                    pipeline_event_consumer = each_node
                    break
            return pipeline_event_consumer
        except Exception as e:
            logger.error("Error occurred while identifying the event consumer for the pipeline : {}".format(str(e)))
            raise Exception("Failed to identify event consumer for the pipeline")

    def fetch_event_consumer_configurations(self, event_consumer):
        """
        This method is to fetch the configurations for the node
        """
        try:
            if "readmsaccess" in event_consumer.lower():

                if "statistics" in self.device_info:
                    self.config['source']['EventConsumer']['host'] = self.device_info['statistics']['ip-address']
                else:
                    self.config['source']['EventConsumer']['host'] = "localhost"

                self.config['source']['EventConsumer']['port'] = Pipeline.ACQUISITION_PORT_KAFKA
                self.config['source']['EventConsumer']['topic'] = Pipeline.ACQUISITION_TOPIC
            event_consumer_configurations = self.config.get('source', dict()).get('EventConsumer', dict())
            return event_consumer_configurations
        except Exception as ce:
            logger.error("Error occurred while fetching the event consumer configurations : {}".format(str(ce)))
            raise Exception("Failed to fetch the event consumer configurations")
