import json
from json import JSONDecodeError
from pathlib import Path
from typing import Any
import arklog
import magic
import requests
from fastapi import APIRouter, Query, Request, Response
from fastapi.responses import JSONResponse
from OMPython import OMCSessionZMQ, ModelicaSystem
from datetime import datetime
import struct
import pyarrow
from pyarrow import parquet

from mocka.artefact import Artefact


def single_simulation_om_python(model_file_path, test_frequency, test_amplitude, sampling_ratio, tolerance, data_cycles, output_stabilisation_time, output):
    arklog.debug(f"Performing simulation")
    arklog.debug(f"\t{test_frequency=}")
    arklog.debug(f"\t{test_amplitude=}")
    arklog.debug(f"\t{sampling_ratio=}")
    sampling_frequency = test_frequency * sampling_ratio
    omc = OMCSessionZMQ()
    model = ModelicaSystem(str(model_file_path),model_file_path.stem)
    # model.buildModel("(V_in.v)|(V_out.v)")
    model.buildModel()
    model.setSimulationOptions([
        f"stepSize={1/sampling_frequency}",
        f"tolerance={tolerance}",
        f"startTime={output_stabilisation_time}",
        f"stopTime={output_stabilisation_time + (data_cycles / test_frequency)}",
    ])
    model.setParameters([
        f"Vt={test_amplitude}",
        f"Ft={test_frequency}",
    ])
    model.simulate()
    samples = model.getSolutions(output)
    # for fileName in os.listdir():
    #     if re.search("notchFilter.*", fileName) and fileName != "notchFilter.mo":
    #         os.remove(fileName)
    return save_data(samples, test_frequency, test_amplitude, sampling_ratio, model_file_path)


def save_data(samples, test_frequency, test_amplitude, sampling_ratio, file_path):
    time = datetime.timestamp(datetime.now())
    common_column_metadata = {
        "test_frequency": struct.pack("d", test_frequency),
        "test_amplitude": struct.pack("d", test_amplitude),
        "sampling_ratio": struct.pack("d", sampling_ratio),
        "time": struct.pack("d", time),
        "type": b"input"
    }
    input_field = pyarrow.field(f"i_{time}", pyarrow.float64(), False, metadata=common_column_metadata)
    common_column_metadata["type"]=b"output"
    output_field = pyarrow.field(f"o_{time}", pyarrow.float64(), False, metadata=common_column_metadata)
    common_column_metadata["type"]=b"time"
    time_field = pyarrow.field(f"ft_{time}",pyarrow.float64(), False, metadata=common_column_metadata)
    # TODO In FTGPM terms we don't want to append to existing experiments
    storage_file_path = Path(__file__).parent.parent.parent / Path("data") / Path(f"{file_path.stem}_results.parquet")
    if storage_file_path.exists():
        experiment_data = parquet.read_table(str(storage_file_path))
        experiment_data = experiment_data.append_column(time_field, [samples[0]]).append_column(output_field, [samples[1]]).append_column(input_field, [samples[2]])
    else:
        experiment_data = pyarrow.Table.from_arrays(samples, schema=pyarrow.schema([time_field, input_field, output_field]))
    parquet.write_table(experiment_data,str(storage_file_path))
    return storage_file_path


class NotchRouter(APIRouter):
    """"""

    def __init__(self, *args: Any, title: str, description: str, version: str, configuration, **kwargs: Any):
        self.title = title
        self.description = description
        self.version = version
        self.configuration = configuration
        super().__init__(*args, **kwargs)

        @self.get("/simulation/")
        async def simulation_notch(request: Request, query: str | None = Query(None)) -> Response:
            """Notch simulation request response for a simulated activity."""
            try:
                body = await request.json()
            except JSONDecodeError:
                return JSONResponse(status_code=500, content={"ctrl": "error", "debug": "Could not parse json input."})
            control = body.get("ctrl")
            # The predefined activity expects the control to be on the cin or rep port.
            if control not in ["cin"]:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": "This activity expects the control flow input on port 'cin'."})
            body_artefacts = body.get("input", {})
            model = body_artefacts.get("model")
            parameters = body_artefacts.get("parameters")
            if not model or not parameters:
                return JSONResponse(status_code=400, content={"ctrl": "error", "debug": "Input artefacts are missing."})

            model_file_path = Path(__file__).parent.parent.parent / Path("data") / Path(model.get("name"))
            model_file_path.write_text(model.get("content"))
            parsed_parameters = json.loads(parameters.get("content"))
            frequency = parsed_parameters.get("frequency")
            amplitude = parsed_parameters.get("amplitude")
            sampling_ratio = parsed_parameters.get("sampling_ratio")
            tolerance = parsed_parameters.get("tolerance")
            data_cycles = parsed_parameters.get("data_cycles")
            output_stabilisation_time = parsed_parameters.get("output_stabilisation_time")
            output = parsed_parameters.get("output")
            arklog.debug(f"{frequency=}")
            arklog.debug(f"{amplitude=}")
            arklog.debug(f"{sampling_ratio=}")
            arklog.debug(f"{tolerance=}")
            arklog.debug(f"{data_cycles=}")
            arklog.debug(f"{output_stabilisation_time=}")
            arklog.debug(f"{output=}")
            artefact_file_path = single_simulation_om_python(model_file_path, frequency, amplitude, sampling_ratio, tolerance,data_cycles,output_stabilisation_time, output)
            requests.put(f"http://localhost:5000/files/file/{artefact_file_path.name}", data=artefact_file_path.read_bytes())
            mime = magic.Magic(mime=True).from_file(artefact_file_path)
            simulation_artefact = Artefact("reference", f"http://localhost:5000/files/file/{artefact_file_path.name}", artefact_file_path.name, mime)
            return JSONResponse(status_code=200, content={"ctrl": "ok", "output": {"experiment": simulation_artefact.as_dict()}})

        @self.put("/simulation/")
        async def simulation_put_notch(request: Request, query: str | None = Query(None)) -> Response:
            return await simulation_notch(request, query)

        @self.post("/simulation/")
        async def simulation_post_notch(request: Request, query: str | None = Query(None)) -> Response:
            return await simulation_notch(request, query)

        @self.get("/validation/")
        async def validation_notch(request: Request, query: str | None = Query(None)) -> Response:
            """Notch simulation request response for a simulated activity."""
            raise NotImplementedError

        @self.put("/validation/")
        async def validation_put_notch(request: Request, query: str | None = Query(None)) -> Response:
            return await validation_notch(request, query)

        @self.post("/validation/")
        async def validation_post_notch(request: Request, query: str | None = Query(None)) -> Response:
            return await validation_notch(request, query)
