"""
This module contains a stubbed out structure for one way of doing a simulation
 for forecasting purposes.

For more sophisticated forecasts where prediction of one timestep
depends on the previous timestep, you need to forecast on a per-step basis. So
you're effectively simulating the future at each step. This is a simple example of
how you might structure such a simulation.
"""

from typing import Tuple

from burr.core import Application, ApplicationBuilder, State, expr
from burr.core.action import action


@action(reads=["data_path"], writes=["data"])
def prepare_data(state: State) -> Tuple[dict, State]:
    """This would pull the data, prepare it, and return it."""
    # pull data, prepare as necessary
    result = {"data": _process_data(state["data_path"])}
    return result, state.update(**result)


@action(reads=["data", "simulation_start"], writes=["model"])
def build_model(state: State) -> Tuple[dict, State]:
    """This would fit the model on data before the simulation start date."""
    training_data = state["data"]
    model = _fit_model(training_data, upto=state["simulation_start"])
    result = {"model": model}
    return result, state.update(**result)


@action(
    reads=["simulation_start", "simulation_end", "model", "data", "current_timestep"],
    writes=["data"],
)
def forecast(state: State) -> Tuple[dict, State]:
    """This action forecasts the next timestep in the simulation and appends it to data."""
    model = state["model"]
    data = state["data"]
    start_time = state["simulation_start"]
    current_time_step = state.get("current_timestep", start_time)
    next_timestep = _forecast_timestep(model, data, current_time_step)
    data = _update_data(data, next_timestep)
    result = {"data": data, "current_timestep": next_timestep}
    return result, state.update(**result)


@action(reads=["data"], writes=[])
def terminate(state: State) -> Tuple[dict, State]:
    """This is a terminal step that would save the forecast or do something else."""
    return {}, state


def application() -> Application:
    app = (
        ApplicationBuilder()
        .with_actions(prepare_data, build_model, forecast, terminate)
        .with_transitions(
            ("prepare_data", "build_model"),
            ("build_model", "forecast"),
            ("forecast", "forecast", expr("current_timestep<simulation_end")),
            ("forecast", "terminate", expr("current_timestep>=simulation_end")),
        )
        .with_state(
            data_path="SOME_PATH",
            simulation_start="some-value",
            simulation_end="some-end-value",
        )
        .with_entrypoint("prepare_data")
        .build()
    )
    return app


# --- stubbed out functions to fill out for the simulation
def _process_data(data_path: str) -> object:
    """This function would read and process data from a file."""
    return {}


def _fit_model(data: object, upto: str) -> object:
    """This function would fit a model on data upto a certain date."""
    return {}


def _forecast_timestep(model: object, data: object, current_timestep: str) -> object:
    """This function would forecast the next timestep in the simulation given the model and data."""
    return {}


def _update_data(data: object, next_timestep: object) -> object:
    """This function would update the data with the forecasted timestep."""
    return {}


if __name__ == "__main__":
    _app = application()
    _app.visualize(
        output_file_path="statemachine", include_conditions=True, view=True, format="png"
    )
    # you could run things like this:
    # last_action, result, state = _app.run(halt_after=["terminate"])
