from dataclasses import dataclass
from pathlib import Path
from typing import List, NamedTuple, Optional

import numpy as np

from app.data_structures.object_detection import DetectedObject
from app.data_structures.time_interval import VideoTimeInterval


class Stage(NamedTuple):
    """
    Represents a one stage of processing pipeline.
    value: unique value of the stage for identification
    label: label of the stage for displaying
    """

    value: str
    label: str

    def __str__(self) -> str:
        return self.label

    def __eq__(self, other) -> bool:
        return self.value == other.value


class ProcessingStages(str):
    """
    Represents all stages of processing pipeline.
    """

    INITIALIZING = Stage("INITIALIZING", "Inicializace")
    MOTION_DETECTION = Stage("MOTION_DETECTION", "Detekce pohybu")
    CUTTING = Stage("CUTTING", "Střih videa")
    OBJECT_DETECTION = Stage("OBJECT_DETECTION", "Detekce objektů")
    FINISHED = Stage("FINISHED", "Dokončeno")

    @classmethod
    def get(cls, item: str) -> Stage:
        """
        Returns a stage by its value.
        :param item: value of the stage
        :return: stage object
        """
        if not hasattr(cls, item):
            raise ValueError(f"{item} is not a valid ProcessingState")
        return getattr(cls, item)


class ProcessingOrder:
    """
    Represents order of processing pipeline stages.
    """

    MOTION_DETECTION = ProcessingStages.MOTION_DETECTION
    CUTTING = ProcessingStages.CUTTING
    OBJECT_DETECTION = ProcessingStages.OBJECT_DETECTION

    @classmethod
    def get_all(cls) -> List[Stage]:
        """
        Returns all stages of processing pipeline.
        :return: list of stages
        """
        return [cls.MOTION_DETECTION, cls.CUTTING, cls.OBJECT_DETECTION]

    @classmethod
    def get_next(cls, current_stage: Stage) -> Stage:
        """
        Returns a next stage of processing pipeline.
        :param current_stage: current stage of processing pipeline
        :return: next stage of processing pipeline
        """
        if current_stage == cls.MOTION_DETECTION:
            return cls.CUTTING
        elif current_stage == cls.CUTTING:
            return cls.OBJECT_DETECTION
        raise ValueError(f"Invalid stage {current_stage}")

    @classmethod
    def get_stage_number(cls, stage: Stage) -> int:
        """
        Returns an index of the stage in processing pipeline.
        :param stage: stage of processing pipeline
        :return: index of the stage
        """
        return cls.get_all().index(stage)


class Configurations(NamedTuple):
    """
    Represents configurations of processing pipeline.
    cut_margin: margin of the cut video in seconds
    object_detection: whether to perform object detection
    """

    cut_margin: int
    object_detection: bool


class ImageToSave(NamedTuple):
    """
    Represents an image to save.
    image: image to save
    file_name: name of the file
    """

    image: np.ndarray
    file_name: str


@dataclass
class DetectionResult:
    """
    Represents a result of detection after all stages of processing pipeline.
    video_path: path to the cut video with detected motion
    interval: time interval of the video
    objects: list of detected objects
    image: image of the detected motion
    image_path: path to the image of the detected motion
    """

    video_path: Path
    interval: VideoTimeInterval
    objects: List[DetectedObject]
    objects_image: ImageToSave
    image_path: Optional[Path] = None


@dataclass
class ProcessingResult:
    """
    Represents a result of processing pipeline.
    video_path: path to the video which was processed
    """

    video_path: Path


@dataclass
class VideoProcessingResult(ProcessingResult):
    """
    Represents a successful result of processing pipeline.
    processing_time_interval: time interval of the processing
    detection_results: list of detection results
    ok: flag to indicate whether the processing was successful
    """

    detection_results: List[DetectionResult]
    ok = True


@dataclass
class UnprocessedVideoResult(ProcessingResult):
    """
    Represents an unsuccessful result of processing pipeline.
    error_message: error message
    ok: flag to indicate whether the processing was unsuccessful
    """

    error_message: str
    ok = False
