import uuid
from typing import Literal, List

from opentrons.protocol_api import WasteChute
from opentrons.protocol_api._types import OffDeckType
from opentrons.protocol_api.core.labware import LabwareLoadParams
from opentrons.types import DeckSlotName

from ..data.data import Data, waste_chute_id, fixed_trash_id

from .helpers import get_location_from_str, convert_generic_to_labware_module_trash


class ProtocolLogic:
    def __init__(self, data: Data):
        self.data = data

    def set_rail_lights(self, on: bool):
        self.data.protocol.set_rail_lights(on)

    @property
    def door_closed(self):
        return self.data.protocol.door_closed

    def clear_commands(self):
        self.data.protocol.clear_commands()

    def commands(self):
        return self.data.protocol.commands()

    def home(self):
        instruments = [self.data.static_items.get_instrument_by_mount("left"),
                       self.data.static_items.get_instrument_by_mount("right"),
                       self.data.static_items.get_instrument_by_mount(None)]

        for instrument in instruments:
            if instrument is not None:
                instrument.instrument_instance.home_plunger()

        return self.data.protocol.home()

    def load_fixed_trash_bin(self):
        if self.data.static_items.has_fixed_trash:
            return fixed_trash_id

        fixed_trash = self.data.protocol.fixed_trash
        return self.data.static_items.register_trash(trash=fixed_trash, is_fixed=True)

    def load_waste_chute(self):
        if self.data.static_items.has_waste_chute:
            return waste_chute_id
        waste_chute = self.data.protocol.load_waste_chute()
        return self.data.static_items.register_trash(trash=waste_chute, is_waste_chute=True)

    def load_trash_bin(self, location: str):
        old_trash_bin = self.data.static_items.get_trash_id_by_deck_location(location)
        if old_trash_bin is not None:
            return old_trash_bin

        trash_bin = self.data.protocol.load_trash_bin(location=get_location_from_str(location))
        return self.data.static_items.register_trash(trash=trash_bin)

    def load_module(self, module_name: str, location: str):
        old_module = self.data.static_items.get_module_by_deck_location(location)
        if old_module is not None and old_module.data.module_name == module_name:
            return self.data.static_items.get_module_id_by_reference(old_module.module_instance)

        module = self.data.protocol.load_module(module_name=module_name,
                                                location=get_location_from_str(location))
        return self.data.static_items.register_module(module=module)

    def load_adapter(self, load_name: str, location: str, namespace: str | None):
        old_adapter = self.data.static_items.get_labware_by_deck_location(location)
        if len(old_adapter) is not 0:
            for key, value in old_adapter.items():
                if value.data.load_name == load_name and value.data.namespace == namespace:
                    return key

        adapter = self.data.protocol.load_adapter(load_name=load_name,
                                                  location=get_location_from_str(location),
                                                  namespace=namespace)

        return self.data.static_items.register_labware(labware=adapter)

    def load_labware(self, load_name: str, location: str, namespace: str | None = None, adapter_name: str | None = None):
        old_labware = self.data.static_items.get_labware_by_deck_location(location)
        if len(old_labware) is not 0:
            for key, value in old_labware.items():
                if value.data.load_name == load_name and value.data.namespace == namespace:
                    return key

        labware = self.data.protocol.load_labware(load_name=load_name,
                                                  location=get_location_from_str(location),
                                                  namespace=namespace,
                                                  adapter=adapter_name
                                                  )

        self.data.dynamic_items.register_wells_by_labware(labware=labware)

        return self.data.static_items.register_labware(labware=labware)

    def load_instrument(self, instrument_name: str, mount: str, tip_racks: List[uuid.UUID]):
        old_instrument = self.data.static_items.get_instrument_by_mount(mount)
        if old_instrument is not None:
            return self.data.static_items.get_instrument_id_by_reference(old_instrument.instrument_instance)

        tip_rack_items = []
        for tip_rack in tip_racks:
            item = self.data.static_items.get_labware_by_id(tip_rack)
            if item is not None:
                tip_rack_items.append(item)

        instrument = self.data.protocol.load_instrument(instrument_name=instrument_name,
                                                        mount=mount,
                                                        tip_racks=tip_rack_items,
                                                        liquid_presence_detection=True)
        return self.data.static_items.register_instrument(instrument=instrument)

    def move_labware(self, labware_id: uuid.UUID, new_location: str,
                     pick_up_x: float = 0, pick_up_y: float = 0, pick_up_z: float = 0,
                     drop_x: float = 0, drop_y: float = 0, drop_z: float = 0):
        labware = self.data.static_items.get_labware_by_id(labware_id)
        self.data.protocol.move_labware(labware=labware.labware_instance,
                                        new_location=get_location_from_str(new_location),
                                        pick_up_offset={"x": pick_up_x, "y": pick_up_y, "z": pick_up_z},
                                        drop_offset={"x": drop_x, "y": drop_y, "z": drop_z},
                                        use_gripper=True)

    def move_labware_to_id(self, labware_id: uuid.UUID, new_location_id: uuid.UUID,
                           pick_up_x: float = 0, pick_up_y: float = 0, pick_up_z: float = 0,
                           drop_x: float = 0, drop_y: float = 0, drop_z: float = 0):
        labware = self.data.static_items.get_labware_by_id(labware_id)
        new_location = self.data.static_items.get_generic_by_id(new_location_id)

        if new_location is None:
            raise RuntimeError("Location id invalid")

        self.data.protocol.move_labware(labware=labware.labware_instance,
                                        new_location=convert_generic_to_labware_module_trash(new_location),
                                        pick_up_offset={"x": pick_up_x, "y": pick_up_y, "z": pick_up_z},
                                        drop_offset={"x": drop_x, "y": drop_y, "z": drop_z},
                                        use_gripper=True)
