from youtube_autonomous.database.local_database_handler import LocalDatabaseHandler
from youtube_autonomous.segments.enums import SegmentStatus, SegmentBuildingField, ProjectStatus, ProjectBuildingField
from youtube_autonomous.dataclasses import ProjectAsJson, MongoResult, ProjectForDatabase
from bson.objectid import ObjectId
from typing import Union


class ProjectDatabaseHandler(LocalDatabaseHandler):
    """
    This class is used to interact with the database
    in which the projects are stored and its
    information and status is handled.

    The projects are stored in the database containing
    the mongo '_id'', a 'status', the 'script' (that
    is the whole json used to generate it and to
    compare other json files to check if existing), and
    the 'segments' field that is the one the app uses to
    handle the building data and this process.

    The 'script' must be preserved as it is, to be able
    to compare jsons and avoid duplicated projects, and
    the 'segments' field must be used by the app to keep
    the building process progress.
    """
    DATABASE_NAME = 'youtube_autonomous'
    PROJECTS_TABLE_NAME = 'projects'

    def __init__(self):
        super().__init__(self.DATABASE_NAME)

    @property
    def first_unfinished_project(self) -> Union[MongoResult, None]:
        result = self.find_one_by_field(
            self.PROJECTS_TABLE_NAME,
            ProjectBuildingField.STATUS.value,
            { '$ne': ProjectStatus.FINISHED.value }
        )

        return MongoResult(result) if result is not None else None
    
    @property
    def unfinished_projects(self) -> list[MongoResult]:
        results = self.find_by_field(
            self.PROJECTS_TABLE_NAME,
            ProjectBuildingField.STATUS.value,
            { '$ne': ProjectStatus.FINISHED.value }
        )

        return [
            MongoResult(result)
            for result in results
        ]
    
    def get_project_by_script(
        self,
        project: ProjectAsJson
    ) -> Union[MongoResult, None]:
        """
        Get the project with the provided json data
        as its 'script' field.

        The script field is where the raw project
        information is stored.
        """
        project = self.find_one_by_field(
            self.PROJECTS_TABLE_NAME,
            'script',
            project.json
        )

        return MongoResult(project) if project is not None else None

    def get_project_by_id(
        self,
        id: Union[ObjectId, str]
    ) -> Union[MongoResult, None]:
        """
        Get the project with the given 'id'.
        """
        project = self.find_one_by_id(
            self.PROJECTS_TABLE_NAME,
            id
        )

        return MongoResult(project) if project is not None else None
    
    def insert_project(
        self,
        project: ProjectForDatabase
    ) ->  Union[MongoResult, None]:
        """
        Insert the provided 'project' in the database.

        This method allow inserting duplicated projects.
        """
        project = self.insert_one(
            self.PROJECTS_TABLE_NAME,
            project.as_dict
        )

        return MongoResult(project) if project is not None else None
    
    def update_project_field(
        self,
        project_id: Union[ObjectId, str],
        field: str,
        value: any
    ) -> Union[MongoResult, None]:
        """
        Update the given 'field' of the project with
        the also provided 'project_id' and set the 
        'value' passed as parameter.
        """
        result = self.update_one(
            table_name = self.PROJECTS_TABLE_NAME,
            id = project_id,
            field = field,
            value = value
        )

        return MongoResult(result) if result is not None else None
    
    # TODO: What does it return if the update is not possible (?)
    def update_project_segment_field(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        field: str,
        value: any
    ) -> Union[MongoResult, None]:
        """
        Update the given 'field' of the 'segment_index'
        segment, that the project with the given 
        'project_id' has, and set the 'value' passed as
        parameter.
        """
        result = self.update_one(
            table_name = self.PROJECTS_TABLE_NAME,
            id = project_id,
            field = f'segments.{str(segment_index)}.{field}',
            value = value
        )

        return MongoResult(result) if result is not None else None

    # TODO: What does it return if the update is not possible (?)
    def update_project_segment_enhancement_field(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        enhancement_index: int,
        field: str,
        value: any
    ) -> Union[MongoResult, None]:
        """
        Update the given 'field' of the enhancement in
        the 'enhancement_index' provided position, which
        is in the 'segment_index' segment of the project
        with the given  'project_id', and set the 'value' passed as
        parameter.
        """
        result = self.update_one(
            table_name = self.PROJECTS_TABLE_NAME,
            id = project_id,
            field = f'segments.{str(segment_index)}.enhancements.{str(enhancement_index)}.{field}',
            value = value
        )

        return MongoResult(result) if result is not None else None
    
    def update_project_segment_status(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        status: str
    ) -> Union[MongoResult, None]:
        """
        Update the segment with the given 'segment_index',
        that belongs to the project with the 'project_id'
        id provided and set its status field with the given
        'status' value.
        """
        return self.update_project_segment_field(
            project_id = project_id,
            segment_index = segment_index,
            field = SegmentBuildingField.STATUS.value,
            value = status
        )
    
    def set_project_segment_as_in_progress(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_segment_status(
            project_id = project_id,
            segment_index = segment_index,
            status = SegmentStatus.IN_PROGRESS.value
        )
    
    def set_project_segment_as_finished(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_segment_status(
            project_id = project_id,
            segment_index = segment_index,
            status = SegmentStatus.FINISHED.value
        )
    
    def set_project_segment_transcription(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        transcription: dict
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_segment_field(
            project_id = project_id,
            segment_index = segment_index,
            field = SegmentBuildingField.TRANSCRIPTION.value,
            value = transcription
        )
    
    def set_project_segment_audio_filename(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        audio_filename: str
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_segment_field(
            project_id = project_id,
            segment_index = segment_index,
            field = SegmentBuildingField.AUDIO_FILENAME.value,
            value = audio_filename
        )
    
    def set_project_segment_video_filename(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        video_filename: str
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_segment_field(
            project_id = project_id,
            segment_index = segment_index,
            field = SegmentBuildingField.VIDEO_FILENAME.value,
            value = video_filename
        )
    
    def set_project_segment_full_filename(
        self,
        project_id: Union[ObjectId, str],
        segment_index: int,
        full_filename: str
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_segment_field(
            project_id = project_id,
            segment_index = segment_index,
            field = SegmentBuildingField.FULL_FILENAME.value,
            value = full_filename
        )
    
    def set_project_music_filename(
        self,
        project_id: Union[ObjectId, str],
        music_filename: str
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_field(
            project_id = project_id,
            field = 'music_filename',
            value = music_filename
        )
    
    def set_project_as_finished(
        self,
        project_id: Union[ObjectId, str]
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_field(
            project_id = project_id,
            field = ProjectBuildingField.STATUS.value,
            value = ProjectStatus.FINISHED.value
        )
    
    def set_project_as_in_progress(
        self,
        project_id: Union[ObjectId, str]
    ) -> Union[MongoResult, None]:
        """
        TODO: Write doc
        """
        return self.update_project_field(
            project_id = project_id,
            field = ProjectBuildingField.STATUS.value,
            value = ProjectStatus.IN_PROGRESS.value
        )