from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple

from ytdl_sub.downloaders.downloader import Downloader
from ytdl_sub.downloaders.youtube.playlist import YoutubePlaylistDownloaderOptions
from ytdl_sub.entries.entry import Entry
from ytdl_sub.utils.chapters import Chapters
from ytdl_sub.utils.chapters import Timestamp
from ytdl_sub.utils.ffmpeg import set_ffmpeg_metadata_chapters
from ytdl_sub.utils.file_handler import FileMetadata
from ytdl_sub.validators.validators import BoolValidator


class YoutubeMergePlaylistDownloaderOptions(YoutubePlaylistDownloaderOptions):
    r"""
    Downloads all videos in a playlist and merges them into a single video.

    Usage:

    .. code-block:: yaml

      presets:
        example_preset:
          youtube:
            # required
            download_strategy: "merge_playlist"
            playlist_url: "https://www.youtube.com/playlist?list=UCsvn_Po0SmunchJYtttWpOxMg"
            # optional
            add_chapters: False

    CLI usage:

    .. code-block:: bash

       ytdl-sub dl \
         --preset "example_preset" \
         --youtube.playlist_url "https://www.youtube.com/playlist?list=UCsvn_Po0SmunchJYtttWpOxMg" \
         --youtube.add_chapters True
    """

    _required_keys = {"playlist_url"}
    _optional_keys = {"add_chapters"}

    def __init__(self, name, value):
        super().__init__(name, value)
        self._add_chapters = self._validate_key_if_present(
            "add_chapters", validator=BoolValidator, default=False
        ).value

    @property
    def add_chapters(self) -> Optional[bool]:
        """
        Optional. Whether to add chapters using each video's title in the merged playlist.
        Defaults to False.
        """
        return self._add_chapters


class YoutubeMergePlaylistDownloader(Downloader[YoutubeMergePlaylistDownloaderOptions]):
    downloader_options_type = YoutubeMergePlaylistDownloaderOptions
    supports_download_archive = False
    supports_subtitles = False
    supports_chapters = False

    @classmethod
    def ytdl_option_defaults(cls) -> Dict:
        """
        Default `ytdl_options`_ for ``merge_playlist``

        .. code-block:: yaml

           ytdl_options:
             ignoreerrors: True  # ignore errors like hidden videos, age restriction, etc
             postprocessors:
               # Convert the videos to mkv format
               - key: "FFmpegVideoConvertor"
                 when: "post_process"
                 preferedformat: "mkv"
               # Concatenate all the playlist videos into a single file
               - key: "FFmpegConcat"
                 when: "playlist"
        """
        return dict(
            super().ytdl_option_defaults(),
            **{
                "postprocessors": [
                    {
                        "key": "FFmpegVideoConvertor",
                        "when": "post_process",
                        "preferedformat": "mkv",
                    },
                    {
                        "key": "FFmpegConcat",
                        "when": "playlist",
                    },
                ],
            },
        )

    def _get_chapters(self, merged_video: Entry, add_chapters: bool) -> FileMetadata:
        titles: List[str] = []
        timestamps: List[Timestamp] = []

        current_timestamp_sec = 0
        for video_entry in merged_video.kwargs("entries"):
            timestamps.append(Timestamp(current_timestamp_sec))
            titles.append(video_entry["title"])

            current_timestamp_sec += video_entry["duration"]

        chapters = Chapters(timestamps=timestamps, titles=titles)

        if not self.is_dry_run and add_chapters:
            set_ffmpeg_metadata_chapters(
                file_path=merged_video.get_download_file_path(),
                chapters=chapters,
                file_duration_sec=merged_video.kwargs("duration"),
            )

        return chapters.to_file_metadata(title="Timestamps of playlist videos in the merged file")

    def _to_merged_video(self, entry_dict: Dict) -> Entry:
        """
        Adds a few entries not included in a playlist entry to make it look like a merged video
        entry_dict
        """
        # Set the upload date to be the latest playlist video date
        entry_dict["upload_date"] = max(
            playlist_entry["upload_date"] for playlist_entry in entry_dict["entries"]
        )
        entry_dict["duration"] = sum(
            playlist_entry["duration"] for playlist_entry in entry_dict["entries"]
        )
        entry_dict["ext"] = (
            entry_dict["requested_downloads"][0]["ext"]
            if "requested_downloads" in entry_dict
            else "mkv"
        )
        entry_dict["webpage_url"] = self.download_options.playlist_url

        return Entry(entry_dict=entry_dict, working_directory=self.working_directory)

    def download(self) -> List[Tuple[Entry, FileMetadata]]:
        """Download a single Youtube video, then split it into multiple videos"""
        url = self.overrides.apply_formatter(self.collection.urls.list[0].url)

        entry_dict = self.extract_info(url=url)
        merged_video = self._to_merged_video(entry_dict=entry_dict)

        merged_video_metadata = self._get_chapters(
            merged_video=merged_video, add_chapters=self.download_options.add_chapters
        )
        return [(merged_video, merged_video_metadata)]
