import network_constants as netconst
from security.token_manager import TokenManager
from db.entities import Layer

import json
import requests
from typing import Dict, List
from threading import Thread

import logging
LOGGER = logging.getLogger(__name__)

class MessageHandler:
    def __init__(self, repository):
        self._repository = repository

    def handle_generic(self, body):
        LOGGER.info(f"Received message: {body}")

        message = None
        try:
            message = json.loads(body)
        except ValueError:
            LOGGER.warning("Message is not in JSON format and is ignored")
            return

        if not 'type' in message:
            LOGGER.warning("Message has no type field and is ignored")
            return
        
        if message['type'] == 'new-trace':
            self.handle_new_trace(message['content'])
        elif message['type'] == 'new-traces-available':
            self.handle_new_traces_available()
        else:
            LOGGER.info("Message Type could not be processed")
    
    def _fetch_layer_information(self, use_case: str, table: str) -> List[Layer]:
        # fetch token for authentication
        jwt_token = TokenManager.getInstance().getToken()
        
        # query schema information
        url = f'https://{netconst.BUSINESS_LOGIC_HOSTNAME}:{netconst.BUSINESS_LOGIC_REST_PORT}/api/use-cases/{use_case}/tables/{table}/layers'

        response = requests.get(
                url,
                verify = False, 
                proxies = { "http":None, "https":None },
                headers = { "Authorization": f"Bearer {jwt_token}"}
        )

        if response.status_code != 200:
            raise ValueError(f"no schema information available {response.status_code}")
        
        layers = [Layer.from_business_logic_dict(row) for row in json.loads(response.text)]

        # update local DB, insert each layer that does not already exists
        for layer in layers:
            print(f"Add layer to DB: {layer.to_serializable_dict(for_db=True)}")
            self._repository.delete_layer(layer)
            self._repository.add_layer(layer)

        return layers

    def handle_new_trace(self, content: Dict):
        if "use_case" not in content or "id" not in content or "properties" not in content or "table" not in content:
            LOGGER.error(f"Missing fields in trace, required fields: (use_case, id, properties, table), given fields: ({content.keys()})")
            return
        
        use_case = content["use_case"]
        table = content["table"]

        # fetch layer information
        layers = self._repository.get_layers_for_use_case_and_table(use_case, table)

        # if no local layers are found, fetch information from server
        if len(layers) == 0:
            layers = self._fetch_layer_information(use_case, table)

        # if there are no layers, abort
        if len(layers) == 0:
            LOGGER.warning(f"No layers available for '{use_case}'.'{table}', ignoring trace.")
            return

        nodes = []
        
        for layer in layers:
            node = {}
                
            for prop in layer.total_properties:
                node[prop] = content["properties"][prop]
                

            node["layer_name"] = layer.layer_name
            node["table"] = layer.table
            node["use_case"] = layer.use_case
            nodes.append(node)
        
        if len(nodes) > 0:
            self._repository.add_layer_nodes(nodes)

    def handle_new_traces_available(self):
        # get all traces and call the Processor
        url = f'http://{netconst.TRACE_RETRIEVAL_HOSTNAME}:{netconst.TRACE_RETRIEVAL_REST_PORT}/api/trace'
        
        # disable university http(s) proxy for name resolution and routing
        session = requests.Session()
        session.trust_env = False
        response = session.get(url)

        if response.status_code == 200:
            traces = response.json()
            # TODO integrate zahras code
            # Thread(target=self._processor.process(traces)).start()            
        else:
            LOGGER.error(f"Could not retrieve JSON from {url} with GET request ({response.status_code})")
