"""
Add
Remove
Replace
"""

"""
Tasks
-----

1) group and process actions by layer                        -- done
2) breakCode output format should be the same as read_file   -- done
3) rework breakcode to handle list of expressions
4) validate if file operation was successful
5) validate if command execution was succesful
"""

"Process Layer by Layer"

import asyncio
from .read_file import read_file
from .break_chunk import breakChunk, getLayerBreakDown, getChunk

from typing import List, Optional, TypedDict
from pydantic import Field

"""
    actions = [
        {
            "action":"add",
            "chunk":None,
            "code":<New code>
        },
        {
            "action":"remove",
            "chunk":<layer_chunk_name>,
            "code":None
        },
        {
            "action":"replace",
            "chunk":<layer_chunk_name>,
            "code":<New code>
        }
    ]
"""

class SingleUpdateAction(TypedDict):
    action : str = Field(description="action to carry out (add | remove | replace )")
    chunk : Optional[str] = Field(description = "name of chunk to use as anchor for update operation")
    code : Optional[str] = Field(description="New code to write to file")


def addLeadingWhiteSpaces(code,count):
    cleaned = []

    if count == 0:
        return code
    
    lines = code['code'].split("\n")
    for line in lines:
        cleaned.append(" "*count+line)

    cleaned = "\n".join(cleaned)

    return {"name":code['name'],"code":cleaned}

def processLayer(layers,layer_chunks,items,file_type="python",root_operation=False):

    if root_operation or len(layers) == 0: 

        # we are at the final layer
        for item in items:  # TODO : process layer actions once
            action = item['action']

            code = item['code']

            if action == "add":
                if item['chunk'] != None:
                    add_after = getChunk(item['chunk'],layer_chunks)
                    
                    if add_after == None:
                        raise ValueError(f"Chunk with name {item['chunk']} not found in file")
                    
                    position = layer_chunks.index(add_after)

                    layer_chunks.insert(position,{"code":code,"name":"new"}) #insert at specified position
                else:
                    layer_chunks.append({"code":code,"name":"new"}) #add code to ending of file

            elif action == "remove" or action == "replace":

                chunk_to_remove = getChunk(item['chunk'],layer_chunks)

                if chunk_to_remove == None:
                    raise ValueError(f"Chunk with name {item['chunk']} not found in {layer_chunks}")
                position = layer_chunks.index(chunk_to_remove)
                if action == "remove":
                    layer_chunks.pop(position)
                else:
                    layer_chunks[position] = {"code":code,"name":layer_chunks[position]['name']}

            else:
                raise ValueError(f"action {action} not recognised")
            
        return layer_chunks 
    else:

        layers = list(layers)
        current = layers.pop(0)

        chunk = getChunk(current,layer_chunks)

        if chunk == None:
            raise ValueError("Could not find code in file") #could not find layer in chunks
        
        inner_layer,whitespaces = asyncio.run(breakChunk(chunk['code'],current,file_type)) #TODO Start here

        result = processLayer(layers,inner_layer,items,file_type)

        prev = result
        if isinstance(result,list):
            prev = result[0]
        else:
            result = [result]

        prev = prev['name'].split("_sub_")
        prev = "_sub_".join(prev[:len(prev)-1]) # get previous layer name

        result_with_identation = [addLeadingWhiteSpaces(i,whitespaces)['code'] for i in result]
        
        chunk_index = layer_chunks.index(getChunk(current,layer_chunks))

        result = {"code":"\n".join(result_with_identation),"name":current}

        layer_chunks[chunk_index] = result

        # remove and add
        return layer_chunks
   
def sortAndGroupByLayer(data):
    # Group by identical chunk_layer array
    groups = {}
    for d in data:
        key = tuple(d.get("chunk_layer", []))  # tuples are hashable, lists aren't
        groups.setdefault(key, []).append(d)


    # Sort each group by number of chunk_layer (descending)
    for k in groups:
        # groups[k] = sorted(groups[k], key=lambda d: len(d.get("chunk_layer", [])), reverse=True)
        actions = sorted(groups[k], key=lambda d: len(d.get("chunk_layer", [])), reverse=True)
        groups[k] = {"actions":actions,"root_chunk":actions[0]['root_chunk']}


    # Sort the groups themselves from largest to smallest chunk_layer count
    sorted_groups = dict(sorted(groups.items(), key=lambda item: len(item[0]), reverse=True))

    # print(sorted_groups, "here with\n")

    return sorted_groups


def addChunkLayersBreakdown(layer_data):
    """
    actions = [
        {
            "action":"add",
            "chunk_layer":[],
            "chunk":None,
            "code":<New code>
        },
        {
            "action":"remove",
            "chunk_layer":[],
            "chunk":<layer_chunk_name>,
            "code":None
        },
        {
            "action":"replace",
            "chunk_layer":[],
            "chunk":<layer_chunk_name>,
            "code":<New code>
        }
    ]
    """

    for i in range(0,len(layer_data)):
        current = layer_data[i]
        layers = getLayerBreakDown(current['chunk'])
        current['chunk_layer'] = layers
        current["root_chunk"] = False

        if len(layers) == 1 and current['chunk'] == layers[0]:
            current["root_chunk"] = True

    return layer_data


def updateFile(filename : str,actions : List[SingleUpdateAction] = []) -> str:

    """
    Update File; write, remove or replace code in file

    Args:
        filename (str) : absolute path to file we are updating
        actions (List[SingleUpdateAction]) : a list of update action to perform on file

    Returns:
        Confirmation if update was successful or not
    """

    file_type = "js"

    if filename.endswith(".py"):
        file_type = "python"
   
    actions = addChunkLayersBreakdown(actions)

    chunks = read_file(filename)

    if chunks == None:
        "TODO : create new file in the specified path"
        file_code = ""
        for step in actions:
            # print(step['action'])
            if step['action'] != "add":
                return "Error : This is a new file : you cannot replace or remove code because its new"
            
            if len(file_code) == 0:
                file_code += step['code']
            else:
                file_code += f"\n\n{step['code']}"

    else:
        sorted_data = sortAndGroupByLayer(actions) #sort by layer, max to min

        layer_chunks = chunks

        for layer in sorted_data:

            layer_actions = sorted_data[layer]['actions']
            is_root = sorted_data[layer]['root_chunk']


            # if is_root:
            #     print("Found root chunk ", layer)
                        
            # check for root operations
            try:
                layer_chunks = processLayer(layer,layer_chunks,layer_actions,file_type,is_root)
                # if root_chunk: # is root operation
                #     layer_chunks = processLayer(layer,layer_chunks,layer_actions,True)
                # else:
                #     # current = getChunk(layer[0],chunks)
                #     # update = getChunk(layer[0],layer_chunks)
                #     # # TODO : verify update here
                #     # print("current ",current,"\n\n")
                #     # print("update ",update,"\n\n")
                #     layer_chunks = processLayer(layer,layer_chunks,layer_actions)  
            except ValueError as err:
                return f"Error : {err}"
           

        file_code = "\n\n".join([i['code'] for i in layer_chunks])

    # write update
    with open(filename,"w") as file:
        file.write(file_code)

    return "Update Successful" # If Successful



# if __name__ == "__main__":
#     # print(getLayerBreakDown("chunk_1"))
#     filename = "/home/attah/.sb/environment/express/controllers/contact_controller.js"
#     chunks = read_file(filename)
#     # layers = getLayerCode(getLayerBreakDown("chunk_1_sub_5_sub_3"),chunks,"js")

#     # for item in layers:
#     #     print("name",item['name'])
#     #     print(item['code'])
#     #     print("\n","-"*30)

#     actions = [
#         {
#             "action":"remove",
#             "chunk":"chunk_1_sub_2",
#             "code":"console.log('Lolo')"
#         },
#         # {
#         #     "action":"add",
#         #     "chunk":"chunk_2", # add before chunk
#         #     "code":"# 'step':'I am the Batman',"
#         # },
#         # {
#         #     "action":"add",
#         #     "chunk":"chunk_3_sub_2_sub_2_sub_2", # replace chunk
#         #     "code":"'step':'Jesus Baby',"
#         # },
#         # {
#         #     "action":"add",
#         #     "chunk":"chunk_3_sub_2_sub_2_sub_3", # replace chunk
#         #     "code":"'step':'Jesus Baby',"
#         # },
#         # {
#         #     "action":"add",
#         #     "chunk":None, # replace chunk
#         #     "code":"New code"
#         # },
#     ]

#     res = updateFile(filename,actions)
#     print(res)

