import time
from enum import Enum
from typing import List, Optional
from uuid import uuid4

from pydantic import BaseModel, Field

from isar.apis.models.models import InputPose, InputPosition
from isar.config.settings import settings
from robot_interface.models.mission.mission import Mission
from robot_interface.models.mission.task import (
    TASKS,
    RecordAudio,
    ReturnToHome,
    TakeCO2Measurement,
    TakeImage,
    TakeThermalImage,
    TakeThermalVideo,
    TakeVideo,
    ZoomDescription,
)


class InspectionTypes(str, Enum):
    image = "Image"
    thermal_image = "ThermalImage"
    video = "Video"
    thermal_video = "ThermalVideo"
    audio = "Audio"
    co2_measurement = "CO2Measurement"


class TaskType(str, Enum):
    Inspection = "inspection"
    ReturnToHome = "return_to_home"


class StartMissionInspectionDefinition(BaseModel):
    type: InspectionTypes = Field(default=InspectionTypes.image)
    inspection_target: InputPosition
    inspection_description: Optional[str] = None
    duration: Optional[float] = None


class StartMissionTaskDefinition(BaseModel):
    id: Optional[str] = None
    type: TaskType = Field(default=TaskType.Inspection)
    pose: InputPose
    inspection: Optional[StartMissionInspectionDefinition] = None
    tag: Optional[str] = None
    zoom: Optional[ZoomDescription] = None


class StartMissionDefinition(BaseModel):
    id: Optional[str] = None
    tasks: List[StartMissionTaskDefinition]
    name: Optional[str] = None
    start_pose: Optional[InputPose] = None


class StopMissionDefinition(BaseModel):
    mission_id: Optional[str] = None


class MissionFormatError(Exception):
    pass


def to_isar_mission(
    start_mission_definition: StartMissionDefinition,
) -> Mission:
    isar_tasks: List[TASKS] = []

    for task_definition in start_mission_definition.tasks:
        task: TASKS = to_isar_task(task_definition)
        isar_tasks.append(task)

    if not isar_tasks:
        raise MissionFormatError("Mission does not contain any valid tasks")

    isar_mission_name: str = (
        start_mission_definition.name
        if start_mission_definition.name
        else _build_mission_name()
    )

    start_pose = None
    if start_mission_definition.start_pose:
        start_pose = start_mission_definition.start_pose.to_alitra_pose()

    id = start_mission_definition.id if start_mission_definition.id else str(uuid4())

    return Mission(
        id=id,
        tasks=isar_tasks,
        name=isar_mission_name,
        start_pose=start_pose,
    )


def to_isar_task(task_definition: StartMissionTaskDefinition) -> TASKS:
    if task_definition.type == TaskType.Inspection:
        return to_inspection_task(task_definition)
    elif task_definition.type == TaskType.ReturnToHome:
        return ReturnToHome()
    else:
        raise MissionFormatError(
            f"Failed to create task: '{task_definition.type}' is not a valid"
        )


def to_inspection_task(task_definition: StartMissionTaskDefinition) -> TASKS:
    if task_definition.inspection is None:
        raise ValueError("Inspection in task definition was None")

    inspection_definition = task_definition.inspection

    if inspection_definition.type == InspectionTypes.image:
        return TakeImage(
            id=task_definition.id if task_definition.id else str(uuid4()),
            robot_pose=task_definition.pose.to_alitra_pose(),
            tag_id=task_definition.tag,
            inspection_description=task_definition.inspection.inspection_description,
            target=task_definition.inspection.inspection_target.to_alitra_position(),
            zoom=task_definition.zoom,
        )
    elif inspection_definition.type == InspectionTypes.video:
        if inspection_definition.duration is None:
            raise ValueError("No duration given to video inspection task")
        return TakeVideo(
            id=task_definition.id if task_definition.id else str(uuid4()),
            robot_pose=task_definition.pose.to_alitra_pose(),
            tag_id=task_definition.tag,
            inspection_description=task_definition.inspection.inspection_description,
            target=task_definition.inspection.inspection_target.to_alitra_position(),
            duration=inspection_definition.duration,
            zoom=task_definition.zoom,
        )
    elif inspection_definition.type == InspectionTypes.thermal_image:
        return TakeThermalImage(
            id=task_definition.id if task_definition.id else str(uuid4()),
            robot_pose=task_definition.pose.to_alitra_pose(),
            tag_id=task_definition.tag,
            inspection_description=task_definition.inspection.inspection_description,
            target=task_definition.inspection.inspection_target.to_alitra_position(),
            zoom=task_definition.zoom,
        )
    elif inspection_definition.type == InspectionTypes.thermal_video:
        if inspection_definition.duration is None:
            raise ValueError("No duration given to video inspection task")
        return TakeThermalVideo(
            id=task_definition.id if task_definition.id else str(uuid4()),
            robot_pose=task_definition.pose.to_alitra_pose(),
            tag_id=task_definition.tag,
            inspection_description=task_definition.inspection.inspection_description,
            target=task_definition.inspection.inspection_target.to_alitra_position(),
            duration=inspection_definition.duration,
            zoom=task_definition.zoom,
        )
    elif inspection_definition.type == InspectionTypes.audio:
        if inspection_definition.duration is None:
            raise ValueError("No duration given to audio inspection task")
        return RecordAudio(
            id=task_definition.id if task_definition.id else str(uuid4()),
            robot_pose=task_definition.pose.to_alitra_pose(),
            tag_id=task_definition.tag,
            inspection_description=task_definition.inspection.inspection_description,
            target=task_definition.inspection.inspection_target.to_alitra_position(),
            duration=inspection_definition.duration,
        )
    elif inspection_definition.type == InspectionTypes.co2_measurement:
        return TakeCO2Measurement(
            id=task_definition.id if task_definition.id else str(uuid4()),
            robot_pose=task_definition.pose.to_alitra_pose(),
            tag_id=task_definition.tag,
            inspection_description=task_definition.inspection.inspection_description,
        )
    else:
        raise ValueError(
            f"Inspection type '{inspection_definition.type}' not supported"
        )


def _build_mission_name() -> str:
    return f"{settings.PLANT_SHORT_NAME}{settings.ROBOT_NAME}{int(time.time())}"
