# ©2025, ANSYS Inc. Unauthorized use, distribution or duplication is prohibited.

import base64

from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
from dash_extensions.enrich import Input, Output, State, Trigger, dash_table, dcc, html
import pandas as pd

from ansys.saf.glow.client import DashClient, callback
from ansys.solutions.camera_app.datamodel.shared.models import ActorAndHid, DesignFields
from ansys.solutions.camera_app.datamodel.shared.websocket_streams import WebsocketStreamListeners
from ansys.solutions.camera_app.solution.data_access import DataAccess
from ansys.solutions.camera_app.solution.definition import Camera_AppSolution

ROOT_NODE_HID = "0"  
"""For this project we expect the root node states to always be a single state with hid "0"."""

# Styles

style_cell_conditional = [
    {"if": {"column_id": "Name"}, "width": "30%", "textAlign": "left"},
    {"if": {"column_id": "Value"}, "width": "70%", "textAlign": "left"},
]

style_actor_status_data_conditional = [
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "Succeeded"'},
        "color": "green",
    },
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "Failed"'},
        "color": "red",
    },
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "Predecessor failed"'},
        "color": "grey",
    },
]

style_project_status_data_conditional = [
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "FINISHED"'},
        "color": "green",
    },
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "RUNNING"'},
        "color": "black",
    },
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "FAILED"'},
        "color": "red",
    },
    {
        "if": {"column_id": "Value", "filter_query": '{Value} eq "NOT STARTED"'},
        "color": "grey",
    },
]

common_data_table_kwargs = {
    "style_cell_conditional": style_cell_conditional,
    "style_as_list_view": True,
    "cell_selectable": False,
    "data": [{"Name": "", "Value": "NOT STARTED"}],
    "columns": [{"name": "Name", "id": "Name"}, {"name": "Value", "id": "Value"}],
}

# Needed to get the conditional coloring of text to work correctly
COMMON_CSS = [{"selector": "div", "rule": "color: inherit"}]

# For hiding the header of the first row
HIDE_HEADER_CSS = COMMON_CSS + [{"selector": "tr:first-child", "rule": "display: none"}]

# Elements with data which will be automatically updated by callbacks

actor_status_table = dash_table.DataTable(
    id="actor-status-table",
    css=HIDE_HEADER_CSS,
    style_data_conditional=style_actor_status_data_conditional,
    **common_data_table_kwargs,
)
project_status_table = dash_table.DataTable(
    id="project-status",
    css=HIDE_HEADER_CSS,
    style_data_conditional=style_project_status_data_conditional,
    **common_data_table_kwargs,
)
project_information_table = dash_table.DataTable(id="project-table", css=COMMON_CSS, **common_data_table_kwargs)
parameters_table = dash_table.DataTable(id="parameter-table", css=COMMON_CSS, **common_data_table_kwargs)

results_download_button = dbc.Button(
    id="results-button", disabled=True, children="Download Result files", color="dark", outline=True
)
result_download = dcc.Download(id="result-download")

result_image = html.Img(id="result-image", style={"width": "200px"})
result_image_loading = dcc.Loading(id="image-loading", type="circle", display="show", children=result_image)

# Forming the layout

layout = html.Div(
    [
        html.H4("Analysis Status"),
        actor_status_table,
        html.Br(),
        html.H4("Status"),
        project_status_table,
        html.Br(),
        html.H4("Project"),
        project_information_table,
        html.Br(),
        html.H4("Parameters"),
        parameters_table,
        html.Br(),
        html.H4("Results"),
        results_download_button,
        html.Br(),
        result_download,
        html.Br(),
        html.Br(),
        html.Br(),
        result_image_loading,
    ],
)


@callback(
    Output("actor-status-table", "data"),
    Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"),
    State("url", "pathname"),
)
def update_actor_status(pathname):
    project = DashClient[Camera_AppSolution].get_project(pathname)

    data_access = DataAccess(project)
    monitoring_data = data_access.monitoring_data

    root_system = data_access.get_project_tree()

    if root_system.is_uninitialized:
        # The project is not initialized yet
        raise PreventUpdate

    status_by_name = {}

    for node in root_system.children:
        if node.is_system:
            continue

        name = node.name
        uid = node.uid
        actor_states = monitoring_data.get_actor_states_ids(uid)

        state_id = actor_states.states_ids[0]
        actor_and_hid = ActorAndHid(actor_uid=uid, hid=state_id)
        actor_info = monitoring_data.get_actor_information_actor(actor_and_hid)
        status = actor_info.processing_state

        status_by_name[name] = status

    df = pd.DataFrame(status_by_name.items(), columns=["Name", "Value"])
    return df.to_dict("records")


@callback(
    Output("project-status", "data"),
    Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"),
    State("url", "pathname"),
)
def update_project_status(pathname):
    project = DashClient[Camera_AppSolution].get_project(pathname)

    monitoring_data = DataAccess(project).monitoring_data

    project_status = monitoring_data.get_osl_project_state()

    df = pd.DataFrame([["Project status", project_status]], columns=["Name", "Value"])
    return df.to_dict("records")


@callback(
    Output("project-table", "data"),
    Trigger(WebsocketStreamListeners.PROJECT_INFORMATION.value, "message"),
    State("url", "pathname"),
)
def update_project_information(pathname):
    project = DashClient[Camera_AppSolution].get_project(pathname)

    monitoring_data = DataAccess(project).monitoring_data

    project_information = monitoring_data.get_project_information()
    project_information_as_dict = project_information.to_dict_by_title()

    df = pd.DataFrame(list(project_information_as_dict.items()), columns=["Name", "Value"])

    return df.to_dict("records")


@callback(
    Output("parameter-table", "data"),
    Output("parameter-table", "columns"),
    Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"),
    State("url", "pathname"),
)
def update_parameter_status(pathname):
    project = DashClient[Camera_AppSolution].get_project(pathname)
    data_access = DataAccess(project)
    root_system = data_access.get_project_tree()

    if root_system.is_uninitialized:
        # The project is not initialized yet
        raise PreventUpdate
    actor_and_hid = ActorAndHid(actor_uid=root_system.uid, hid=ROOT_NODE_HID)
    design_points = data_access.monitoring_data.get_design_points(actor_and_hid)
    parameters_dict = design_points.to_dict_by_states(DesignFields.PARAMETERS)
    if not parameters_dict:
        raise PreventUpdate
    
    states = design_points.get_states()

    if not len(states) == 1:
        # For this case, we assume only one state for the root node
        raise ValueError(f"Expected only one state, got {len(states)}: {states}")
    
    state = states[0]

    df = pd.DataFrame(parameters_dict)

    # Rename the column "state" to "Value"
    df = df.rename(columns={state: "Value"})
    columns = [{"name": i, "id": i} for i in df.columns]

    return df.to_dict("records"), columns


@callback(
    Output("results-button", "disabled"),
    Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"),
    State("url", "pathname"),
)
def update_results_button_disabled(pathname):
    project = DashClient[Camera_AppSolution].get_project(pathname)

    root_uid = DataAccess(project).get_project_tree().uid
    actor_and_hid = ActorAndHid(actor_uid=root_uid, hid=ROOT_NODE_HID)

    results = DataAccess(project).results
    file_reference = results.get_file_reference_by_name("Result_files.zip", actor_and_hid)
    return file_reference is None


@callback(
    Output("result-download", "data"),
    Input("results-button", "n_clicks"),
    State("url", "pathname"),
)
def download_file(n_clicks, pathname):
    if n_clicks is None:
        raise PreventUpdate

    project = DashClient[Camera_AppSolution].get_project(pathname)

    root_uid = DataAccess(project).get_project_tree().uid
    actor_and_hid = ActorAndHid(actor_uid=root_uid, hid=ROOT_NODE_HID)

    results = DataAccess(project).results
    content = results.get_file_content_by_name("Result_files.zip", actor_and_hid)

    if content is None:
        return dcc.send_string("No result files available", "Error.txt")

    return dcc.send_bytes(content, "Result_files.zip")


@callback(
    Output("image-loading", "display"),
    Output("result-image", "src"),
    Trigger(WebsocketStreamListeners.PROJECT_STATE.value, "message"),
    Trigger(WebsocketStreamListeners.ACTOR_DATA.value, "message"),
    State("url", "pathname"),
)
def update_images(pathname):
    project = DashClient[Camera_AppSolution].get_project(pathname)

    root_uid = DataAccess(project).get_project_tree().uid
    actor_and_hid = ActorAndHid(actor_uid=root_uid, hid=ROOT_NODE_HID)

    results = DataAccess(project).results
    file_content = results.get_file_content_by_name("Camera.svg", actor_and_hid)

    if file_content is None:
        return "show", None

    b64_encoded_image = base64.b64encode(file_content)
    decoded = b64_encoded_image.decode("ascii")
    img_src = f"data:image/svg+xml;base64,{decoded}"

    return "hide", img_src
