#global imports
from db.entities.layer_adapter import LayerAdapter
from db.repository import Repository
from db.table_repository import TableRepository
from db.use_case_repository import UseCaseRepository
from services.layer_adapter_service import LayerAdapterService

import json
from flask import Response, request

table_repository = TableRepository()
layer_repository = Repository()
use_case_repository = UseCaseRepository()

def all():
    return [layer.to_serializable_dict() for layer in layer_repository.all()]


def delete_all_layers():
    '''
    delete all layers from the DB
    '''
    layer_repository.delete_all()
    return Response(status=200)

def add_complete():
    '''
    add a layer already containing attribute mappings to the BD.
    '''
    data = request.json

    if "name" not in data or "properties" not in data or "cluster_properties" not in data or "use_case" not in data or "table" not in data:
        return Response(status=400, response=f"Field missing! Fields required: (name, properties, cluster_properties, table), present:({data.keys()})")

    use_case = data["use_case"]
    table_name = data["table"]
    layer_name = data["name"]

    table = table_repository.get_for_use_case_and_name(use_case, table_name)

    if table == None:
        return Response(status=400, response="Table does not exist!")

    layer = layer_repository.one(layer_name, use_case, table.name)

    if layer != None:
        return Response(status=400, response=f'Layer with name "{data["name"]}" already exists!')

    # check if table contains mapping
    for prop in data["properties"]:
        if prop not in table.mappings.keys():
            return Response(status=400, response=f"'{prop}' is no property of the Table!")

    try:
        layer_new = LayerAdapter.from_serializable_dict(data)
    except BaseException as e:
        print(f"Exception: {e}")
        return Response(status=400, response=f"{e}")
    
    try:
        LayerAdapterService.add_complete(layer_new)
    except ValueError as e:
        return Response(status = 400, response=f"{e}")
    
    return Response(status=200)


def add_cluster_mapping(use_case: str, table: str, name: str):
    '''
    add a mapped property to the list of properties to cluster with

    @params:
        use_case    - Required : String-identifier for the Use-Case the Layer belongs to
        table       - Required : unique identifier of the Table the Layer belongs to
        name        - Required : unique identifier for the Layer
    '''
    layer = layer_repository.one(name, use_case, table)

    if layer == None:
        return Response(status=404, response=f"Layer with name '{name}' does not exist!")

    data = request.json

    if "attribute" not in data:
        return Response(status=400, response=f"Field missing! Fields required: (attribute)")

    try:
        layer.add_cluster_mapping(data["attribute"])
        layer_repository.update(layer)
        return Response(status=200)
    except:
        return Response(status=400, response=f'{data["attribute"]} is no attribute in the layer!')

def delete_cluster_mapping(use_case: str, table: str, name: str):
    '''
    remove a mapped property from the list of properties to cluster with

    @params:
        use_case    - Required : String-identifier for the Use-Case the Layer belongs to
        table       - Required : unique identifier of the Table the Layer belongs to
        name        - Required : unique identifier for the Layer
    '''
    layer = layer_repository.one(name, use_case, table)

    if layer == None:
        return Response(status=404, response=f"Layer with name '{name}' does not exist!")

    data = request.json

    if "attribute" not in data:
        return Response(status=400, response=f"Field missing! Fields required: (attribute)")

    try:
        layer.delete_cluster_mapping(data["attribute"])
        layer_repository.update(layer)
        return Response(status=200)
    except ValueError as e:
        print(e)
        return Response(status=400, response=f'{data["attribute"]} is no attribute in the layer!')


def get_all_for_use_case(use_case: str):
    '''
    get all layers assigned to the given use_case
    '''
    use_case_repository.put(use_case)
    return [layer.to_serializable_dict() for layer in layer_repository.all_for_use_case(use_case)]

def one(use_case: str, table: str, name: str):
    '''
    fetch a single layer from the DB

    @params:
        use_case    - Required : String-identifier for the Use-Case the Layer belongs to
        table       - Required : unique identifier of the Table the Layer belongs to
        name        - Required : unique identifier for the Layer
    '''
    layer = layer_repository.one(name, use_case, table)

    if layer == None:
        return Response(status=404, response=f"Layer with name '{name}' does not exist!")

    return Response(status=200, response=json.dumps(layer.to_serializable_dict()))

def delete_mapping(use_case: str, table: str, name: str):
    '''
    delete a mapping from the layer identified by the internal representation

    @params:
        use_case    - Required : String-identifier for the Use-Case the Layer belongs to
        table       - Required : unique identifier of the Table the Layer belongs to
        name        - Required : unique identifier for the Layer
    '''
    use_case_repository.put(use_case)

    layer = layer_repository.one(name, use_case, table)

    if layer == None:
        return Response(status=404, response=f"Layer with name '{name}' does not exist!")

    data = request.json

    if "internal" not in data:
        return Response(status=400, response=f"Field missing! Fields required: (internal)")

    try:
        layer.delete_mapping(data["internal"])
        layer_repository.update(layer)
    except ValueError:
        return Response(status=400, response=f'{data["internal"]} is not a property of the layer!')

    return Response(status=200)

def add_mapping(name: str, table: str, use_case: str):
    '''
    add a new mapping to the layer identified by name

    @params:
        use_case    - Required : String-identifier for the Use-Case the Layer belongs to
        table       - Required : unique identifier of the Table the Layer belongs to
        name        - Required : unique identifier for the Layer
    '''
    use_case_repository.put(use_case)

    layer = layer_repository.one(name, use_case)

    if layer == None:
        return Response(status=404, response=f"Layer with name '{name}' does not exist!")

    data = request.json

    if "internal" not in data:
        return Response(status=400, response=f"Field missing! Fields required: (internal)")

    # check if schema contains mapping
    table = table_repository.get_for_use_case_and_name(use_case, table)

    if table == None:
        return Response(status=400, response=f'Table does not exist.')

    if data["internal"] not in table.mappings:
        return Response(status=400, response=f'{data["internal"]} is not existent in the table!')

    layer.add_mapping(data["internal"])
    layer_repository.update(layer)

    return Response(status=200)

def delete_one(use_case: str, table: str, name: str):
    '''
    delete a layer and all its mappings from the Db

    @params:
        use_case    - Required : String-identifier for the Use-Case the Layer belongs to
        table       - Required : unique identifier of the Table the Layer belongs to
        name        - Required : unique identifier for the Layer
    '''
    
    layer = layer_repository.one(name, use_case, table)

    if layer == None:
        return Response(status=404, response=f"Layer with name '{name}' does not exist!")

    layer_repository.delete(layer)
    return Response(status=200)

def get_layers_for_use_case_and_table(use_case: str, table: str):
    return [layer.to_serializable_dict() for layer in layer_repository.all_for_use_case_and_table(use_case, table)]