from stringstorage import gettext as _
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GLib
import threading
from concurrent.futures import ThreadPoolExecutor
import os
import yt_dlp
import base64
import json

from download.actionrow import on_download_clicked
from download.thread import DownloadThread

def format_filesize(filesize):
    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
        if filesize < 1024.0:
            return f"{filesize:.2f} {unit}"
        filesize /= 1024.0
    return f"{filesize:.2f} TB"

def cookies_to_netscape(cookies, app):
    if app.appconf["cookies_txt"] == "1":
        with open(os.path.join(app.appdir, 'cookies.txt'), 'r') as file:
            lines = file.readlines()
        cookies_included = True
    else:
        lines = ["# Netscape HTTP Cookie File"]
        cookies_included = False

    try:
        cookies = json.loads(base64.b64decode(cookies).decode("utf-8"))

        for cookie in cookies:
            domain = cookie["domain"]
            include_subdomains = "TRUE" if domain.startswith(".") else "FALSE"
            path = cookie.get("path", "/")
            secure = "TRUE" if cookie.get("secure") else "FALSE"
            expiry = int(cookie.get("expirationDate", 0))

            lines.append("\t".join([
                domain,
                include_subdomains,
                path,
                secure,
                str(expiry),
                cookie["name"],
                cookie["value"]
            ]))
        
        cookies_included = True
    except:
        pass

    if cookies_included:
        final_file_contents = "\n".join(lines)

        with open(os.path.join(app.appdir, "cookies_for_ytdlp.txt"), "w") as text_file:
            text_file.write(final_file_contents)
    
    return cookies_included

def on_video_clicked(button, self, entry, header):
    self.video_loading_cancelled = False

    if isinstance(entry, str):
        url = entry
    
    else:
        url = entry.get_text()
        entry.set_text("")
    
    cookies_included = cookies_to_netscape(header, self) # Generate final cookies file
    
    def loading_dialog_cancel_pressed(dialog, response_id, self):
        self.video_loading_cancelled = True
        GLib.idle_add(dialog.set_can_close, True)
        GLib.idle_add(dialog.close)

    # Show loading screen
    loading_dialog = Adw.AlertDialog()
    loading_dialog_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=25)
    loading_dialog.set_extra_child(loading_dialog_box)
    loading_dialog_box.set_margin_top(30)
    loading_dialog_box.set_margin_bottom(30)
    loading_dialog_box.set_margin_start(60)
    loading_dialog_box.set_margin_end(60)
    loading_dialog_spinner = Adw.Spinner()
    loading_dialog_spinner.set_size_request(30, 30)
    loading_dialog_box.append(loading_dialog_spinner)
    loading_dialog_label = Gtk.Label(label=_("Checking video..."))
    loading_dialog_label.add_css_class("title-1")
    loading_dialog_box.append(loading_dialog_label)

    loading_dialog.add_response("cancel",  _("Cancel"))
    loading_dialog.connect("response", loading_dialog_cancel_pressed, self)
    loading_dialog.set_response_appearance("cancel", Adw.ResponseAppearance.DESTRUCTIVE)

    loading_dialog.set_can_close(False)
    loading_dialog.present(self)

    def ytdlp_startsubprocess():
        youtube_dl_options = {
            'js_runtimes': {self.jsruntimeexec['name']: {'path': self.jsruntimeexec['exec']}},
            'skip_download': True,
            'extract_flat': True
        }

        if cookies_included:
            youtube_dl_options['cookiefile'] = os.path.join(self.appdir, "cookies_for_ytdlp.txt")
        
        def ytdlp_get_data():
            try:
                with yt_dlp.YoutubeDL(youtube_dl_options) as ydl:
                    return {
                        "data": ydl.sanitize_info(ydl.extract_info(url, download=False)),
                        "ytdlp_error": ""
                    }

            except Exception as error:
                return {
                    "data": None,
                    "ytdlp_error": str(error)
                }
        
        second_round_with_no_extract_flat = False # We run the first round with extract_flat because
                                                  # otherwise with playlists yt-dlp tries to extract
                                                  # info from every video in the playlist one by one
                                                  # but single vides require extract_flat to be False
                                                  # to get the formats list, so if the download isn't
                                                  # a playlist we run it again with extract_flat set to False

        while(True):
            executor = ThreadPoolExecutor(max_workers=1)
            future = executor.submit(ytdlp_get_data)

            while not future.done():
                if self.video_loading_cancelled:
                    return
            
            result = future.result()
            data = result["data"]
            ytdlp_error = result["ytdlp_error"]

            if second_round_with_no_extract_flat:
                break

            if data and data.get("_type") == "playlist":
                break

            youtube_dl_options['extract_flat'] = False
            second_round_with_no_extract_flat = True

        video_formats = []
        audio_formats = []
        combined_formats = [] # Combined formats will end up in matched formats at the end
        matched_formats = []

        video_download_options_preferences_dialog = Adw.PreferencesDialog()
        
        # If everything is correct, start building the lists:
        if data:
            video_title = self.escape_special_characters(data.get("title", "?"))
            if data.get("_type") == "video":
                uploader_name = data.get("uploader", "?")

                for fmt in data.get("formats", []):
                    print(fmt)
                    # If filesize is only approximate, take that as filesize
                    if fmt.get("filesize", 0) == 0 and fmt.get("filesize_approx", 0) != 0:
                        fmt["filesize"] = fmt["filesize_approx"]

                    if fmt.get("filesize", 0) == 0:
                        fmt["filesize"] = 0

                    # Throw out unnecessary formats
                    if fmt.get("ext") == "mhtml":
                        continue
                    
                    # Video only
                    elif fmt["audio_ext"] == "none" and fmt["resolution"]:
                        bitrate = fmt.get("vbr", 0)
                        if bitrate == 0 and fmt.get("tbr", 0) != 0:
                            bitrate = fmt.get("tbr", 0)
                        if bitrate != 0:
                            video_formats.append({
                                "id": fmt["format_id"],
                                "ext": fmt["ext"],
                                "resolution": fmt["resolution"],
                                "framerate": fmt.get("fps", None),
                                "filesize": fmt["filesize"],
                                "url": fmt["url"],
                                "bitrate": bitrate,
                            })

                    # Audio only
                    elif fmt["video_ext"] == "none":
                        bitrate = fmt.get("abr", 0)
                        if bitrate == 0 and fmt.get("tbr", 0) != 0:
                            bitrate = fmt.get("tbr", 0)
                        if bitrate != 0:
                            audio_formats.append({
                                "id": fmt["format_id"],
                                "ext": fmt["ext"],
                                "bitrate": bitrate,
                                "filesize": fmt["filesize"],
                                "url": fmt["url"],
                            })

                    # Video and audio
                    elif fmt["video_ext"] != "none" and fmt["audio_ext"] != "none" and fmt["resolution"]:
                        if fmt.get("tbr", 0) != 0:
                            combined_formats.append({
                                "id": fmt["format_id"],
                                "ext": fmt["ext"],
                                "resolution": fmt["resolution"],
                                "framerate": fmt.get("fps", None),
                                "filesize": fmt["filesize"],
                                "url": fmt["url"],
                                "bitrate": fmt.get("tbr", 0)
                            })

                # Match the video-only streams with the appropriate audio-only streams
                for video in video_formats:
                    for audio in audio_formats:
                        if (video["ext"] == audio["ext"]) or (video["ext"] == "mp4" and audio["ext"] == "m4a"):
                            uncertain_size = False
                            if video["filesize"] == None:
                                video["filesize"] = 0
                                uncertain_size = True
                            if audio["filesize"] == None:
                                audio["filesize"] = 0
                                uncertain_size = True
                            total_size_bytes = video["filesize"] + audio["filesize"]
                            bitrate = str(video["bitrate"]) + " - " + str(audio["bitrate"])
                            matched_formats.append([video, audio, total_size_bytes, bitrate, uncertain_size])

                # Add combined formats to the matched formats list
                for combined in combined_formats:
                    combined_size_bytes = combined["filesize"]
                    bitrate = combined["bitrate"]
                    matched_formats.append([combined, None, combined_size_bytes, bitrate])
                
                # Sort the lists based on their filesize, from biggest to smallest
                video_formats.sort(key=lambda x: ((int(x["resolution"].split('x')[0]) * int(x["resolution"].split('x')[1])), x["filesize"]), reverse=True)
                audio_formats.sort(key=lambda x: x["filesize"], reverse=True)
                matched_formats.sort(key=lambda x: ((int(x[0]["resolution"].split('x')[0]) * int(x[0]["resolution"].split('x')[1])), x[2]), reverse=True)

                def remove_duplicates_from_lists(list, mode):
                    seen = set()
                    deduplicated_list = []
                    
                    for item in list:
                        if mode == 0:
                            identifier = (item["resolution"], item["bitrate"], item["filesize"])
                            if identifier not in seen:
                                seen.add(identifier)
                                deduplicated_list.append(item)
                        
                        elif mode == 1:
                            identifier = (item["bitrate"], item["filesize"])
                            if identifier not in seen:
                                seen.add(identifier)
                                deduplicated_list.append(item)
                        
                        else:
                            identifier = (item[2], item[3])
                            if identifier not in seen:
                                seen.add(identifier)
                                deduplicated_list.append(item)
                    
                    return deduplicated_list

                # Remove unnecessary duplicates
                video_formats = remove_duplicates_from_lists(video_formats, 0)
                audio_formats = remove_duplicates_from_lists(audio_formats, 1)
                matched_formats = remove_duplicates_from_lists(matched_formats, 2)

                # Start building the video download dialog
                options_available = False

                if len(matched_formats) > 0:
                    page_video_and_audio = Adw.PreferencesPage(title=_("Video and audio"), icon_name="media-playback-start-symbolic")
                    video_download_options_preferences_dialog.add(page_video_and_audio)
                    group_video_and_audio = Adw.PreferencesGroup(title="\"" + video_title + "\" - " + uploader_name)
                    page_video_and_audio.add(group_video_and_audio)

                    video_download_options_preferences_dialog.set_title(_("Video and audio"))
                    options_available = True

                if len(video_formats) > 0:
                    page_video_only = Adw.PreferencesPage(title=_("Video only"), icon_name="camera-video-symbolic")
                    video_download_options_preferences_dialog.add(page_video_only)
                    group_video_only = Adw.PreferencesGroup(title="\"" + video_title + "\" - " + uploader_name)
                    page_video_only.add(group_video_only)

                    video_download_options_preferences_dialog.set_title(_("Video only"))
                    options_available = True

                if len(audio_formats) > 0:
                    page_audio_only = Adw.PreferencesPage(title=_("Audio only"), icon_name="folder-music-symbolic")
                    video_download_options_preferences_dialog.add(page_audio_only)
                    group_audio_only = Adw.PreferencesGroup(title="\"" + video_title + "\" - " + uploader_name)
                    page_audio_only.add(group_audio_only)

                    video_download_options_preferences_dialog.set_title(_("Audio only"))
                    options_available = True

                def create_option_actionrow(self, group, title, subtitle, type, download_object, download_name, video_download_options_preferences_dialog, url, DownloadThread):
                    video_option_actionrow = Adw.ActionRow()
                    video_download_button = Gtk.Button(label=_("Download"))
                    video_download_button.add_css_class("suggested-action")
                    video_download_button.set_halign(Gtk.Align.START)
                    video_download_button.set_valign(Gtk.Align.CENTER)
                    video_option_actionrow.set_title(title)
                    video_option_actionrow.set_subtitle(subtitle)
                    video_download_button.connect("clicked", lambda clicked, type=type, download_object=download_object, download_name=download_name: on_video_option_download_clicked(self, video_download_options_preferences_dialog, type, download_object, download_name, url))
                    video_option_actionrow.add_suffix(video_download_button)

                    group.add(video_option_actionrow)

                # For video only formats
                for video_only_format in video_formats:
                    actionrow_title = _("Video only")
                    if video_only_format["resolution"] is not None:
                        actionrow_title += "  ·  " + video_only_format["resolution"]
                    if video_only_format["framerate"] is not None:
                        actionrow_title += "@" + str(round(float(video_only_format["framerate"])))

                    actionrow_subtitle = ""
                    if video_only_format["ext"] is not None:
                        actionrow_subtitle = video_only_format["ext"]
                    if video_only_format["filesize"] is not None:
                        formatted_filesize = str(format_filesize(video_only_format["filesize"]))
                        if '0.00' in formatted_filesize:
                            formatted_filesize = _("Unknown filesize")
                        actionrow_subtitle += "  ·  " + formatted_filesize
                    if video_only_format["bitrate"] is not None:
                        actionrow_subtitle += "  ·  " + _("Bitrate:") + " " + str(format_filesize(video_only_format["bitrate"]))
                    
                    create_option_actionrow(self,
                        group_video_only,
                        actionrow_title,
                        actionrow_subtitle,
                        "video",
                        video_only_format,
                        video_title,
                        video_download_options_preferences_dialog, url, DownloadThread
                    )
                
                # For audio only formats
                for audio_only_format in audio_formats:
                    actionrow_title = _("Audio only")
                    
                    actionrow_subtitle = ""
                    if audio_only_format["ext"] is not None:
                        actionrow_subtitle = audio_only_format["ext"]
                    if audio_only_format["filesize"] is not None:
                        formatted_filesize = str(format_filesize(audio_only_format["filesize"]))
                        if '0.00' in formatted_filesize:
                            formatted_filesize = _("Unknown filesize")
                        actionrow_subtitle += "  ·  " + formatted_filesize
                    if audio_only_format["bitrate"] is not None:
                        actionrow_subtitle += "  ·  " + _("Bitrate:") + " " + str(format_filesize(audio_only_format["bitrate"]))
                    
                    create_option_actionrow(self,
                        group_audio_only,
                        actionrow_title,
                        actionrow_subtitle,
                        "audio",
                        audio_only_format,
                        video_title,
                        video_download_options_preferences_dialog, url, DownloadThread
                    )
                
                # For complete (matched) formats
                for complete_format in matched_formats:
                    actionrow_title = _("Video and audio")
                    if complete_format[0]["resolution"] is not None:
                        actionrow_title += "  ·  " + complete_format[0]["resolution"]
                    if complete_format[0]["framerate"] is not None:
                        actionrow_title += "@" + str(round(float(complete_format[0]["framerate"])))
                    
                    actionrow_subtitle = ""
                    if complete_format[0]["ext"] is not None:
                        actionrow_subtitle = complete_format[0]["ext"]
                    if complete_format[2] is not None:
                        formatted_filesize = str(format_filesize(complete_format[2]))
                        if '0.00' in formatted_filesize:
                            formatted_filesize = _("Unknown filesize")
                        actionrow_subtitle += "  ·  " + formatted_filesize
                    if complete_format[4] == True:
                        actionrow_subtitle += "?"
                    if complete_format[3] is not None:
                        actionrow_subtitle += "  ·  " + _("Bitrate:") + " " + str(complete_format[3])
                    
                    create_option_actionrow(self,
                        group_video_and_audio,
                        actionrow_title,
                        actionrow_subtitle,
                        "complete",
                        complete_format,
                        video_title,
                        video_download_options_preferences_dialog, url, DownloadThread
                    )
            
            elif data.get("_type") == "playlist":
                if data["entries"] and len(data["entries"]) > 0:
                    options_available = True
                
                    all_height_qualities = [
                        _("Maximum available quality"),
                        "2160p",
                        "1440p",
                        "1080p",
                        "720p",
                        "480p",
                        "360p",
                        "240p",
                        "144p"
                    ]
                    all_height_qualities_dropdown = Gtk.DropDown.new_from_strings(all_height_qualities)

                    max_quality_actionrow = Adw.ActionRow(title=_("Maximum Quality"))
                    max_quality_actionrow.add_suffix(all_height_qualities_dropdown)

                    mode_toggle_video_and_audio = Adw.Toggle(label=_("Video and audio"))
                    mode_toggle_video_only = Adw.Toggle(label=_("Video only"))
                    mode_toggle_audio_only = Adw.Toggle(label=_("Audio only"))
                    mode_toggle_group = Adw.ToggleGroup()
                    mode_toggle_group.add(mode_toggle_video_and_audio)
                    mode_toggle_group.add(mode_toggle_video_only)
                    mode_toggle_group.add(mode_toggle_audio_only)

                    playlist_download_button = Gtk.Button(label=_("Download"))
                    playlist_download_button.add_css_class("suggested-action")
                    playlist_download_button.add_css_class("pill")
                    playlist_download_button.connect("clicked", lambda clicked: on_playlist_download_clicked(self, video_download_options_preferences_dialog, mode_toggle_group.get_active(), len(data["entries"]), all_height_qualities, all_height_qualities_dropdown.get_selected(), video_title, url))
                    
                    playlist_page = Adw.PreferencesPage()
                    playlist_page_group_1 = Adw.PreferencesGroup()
                    playlist_page_group_2 = Adw.PreferencesGroup()
                    playlist_page_group_3 = Adw.PreferencesGroup()

                    playlist_page_group_1.set_title(video_title)
                    
                    playlist_page_group_1.add(mode_toggle_group)
                    playlist_page_group_2.add(max_quality_actionrow)
                    playlist_page_group_3.add(playlist_download_button)

                    playlist_page.add(playlist_page_group_1)
                    playlist_page.add(playlist_page_group_2)
                    playlist_page.add(playlist_page_group_3)

                    video_download_options_preferences_dialog.set_title(_("Playlist Download"))
                    video_download_options_preferences_dialog.add(playlist_page)
                
                else:
                    options_available = False
            
            # If there are no options available:

            if options_available == False:
                video_download_options_preferences_dialog.set_title("")
                page_error = Adw.PreferencesPage()
                video_download_options_preferences_dialog.add(page_error)
                group_error = Adw.PreferencesGroup()
                page_error.add(group_error)

                error_status_page = Adw.StatusPage(icon_name="dialog-error-symbolic")
                error_status_page.set_title(_("No video or audio streams found"))
                error_status_page.set_hexpand(True)
                error_status_page.set_vexpand(True)

                group_error.add(error_status_page)
            
            GLib.idle_add(loading_dialog.set_can_close, True)
            GLib.idle_add(loading_dialog.close)

            if self.use_ssd:
                video_download_options_preferences_dialog.set_content_width(self.get_default_size()[0])
                video_download_options_preferences_dialog.set_content_height(self.get_default_size()[1])
            GLib.idle_add(video_download_options_preferences_dialog.present, self)

        else:
            # Show error dialog if something went wrong
            error_body = (
                f"{_("An error occurred:")}\n\n"
                f"{_("The video platform may not be supported or you may be being blocked by the platform. If you have an active VPN connection that might be the cause, or you can try importing your browser's cookies through a cookies.txt file.")}\n\n\n"
                "────────────────────────────\n\n\n"
                f"{self.escape_special_characters(ytdlp_error)}"
            )
            error_dialog = Adw.AlertDialog()
            error_dialog.set_body(error_body)
            error_dialog.add_response("ok",  _("OK"))
            error_dialog.set_response_appearance("ok", Adw.ResponseAppearance.SUGGESTED)
            error_dialog.set_close_response("ok")
            GLib.idle_add(error_dialog.present, self)

        GLib.idle_add(loading_dialog.set_can_close, True)
        GLib.idle_add(loading_dialog.close)

        return

    thread = threading.Thread(target=ytdlp_startsubprocess, daemon=True)
    thread.start()

def on_video_option_download_clicked(self, prefswindow, type, download_object, download_name, url):
    # Start downloading once the user clicks download on an option

    prefswindow.close()
    print(url)

    # Unless they were already complete streams before getting matched,
    # complete formats require both a video and audio streams
    if type == "complete":
        if download_object[1] == None: # Format already includes an audio stream
            video_options = {
                'format': download_object[0]["id"],
                'filesize_to_show': str(format_filesize(download_object[0].get("filesize", 0)))
            }

        else: # Video and audio formats are separate
            video_options = {
                'format': download_object[0]["id"] + "+" + download_object[1]["id"],
                'filesize_to_show': str(format_filesize(download_object[0].get("filesize", 0) + download_object[1].get("filesize", 0)))
            }
        
        file_ext = download_object[0]["ext"]

    else:
        video_options = {
            'format': download_object["id"],
            'filesize_to_show': str(format_filesize(download_object.get("filesize", 0)))
        }

        file_ext = download_object["ext"]
    
    filename = download_name + "." + file_ext
    
    # Check if filename already exists
    while True:
        if os.path.exists(os.path.join(self.appconf["download_directory"], filename)):
            filename = download_name + "-1." + file_ext

        else:
            break

    on_download_clicked(None, self, url, filename, None, "video", video_options, False, self.appconf["download_directory"], 0)

def on_playlist_download_clicked(self, prefswindow, type, length, qualities, selected_quality_index, download_name, url):
    prefswindow.close()
    
    if selected_quality_index == 0:
        format_quality_text = ""
    else:
        quality_resolution = qualities[selected_quality_index].replace("p", "")
        format_quality_text = f"[height<={quality_resolution}]"
    
    if type == 0:
        format_text = f"bestvideo{format_quality_text}+bestaudio/best{format_quality_text}"
    elif type == 1:
        format_text = f"bestvideo{format_quality_text}"
    elif type == 2:
        format_text = f"bestaudio/best{format_quality_text}"

    video_options = {
        'length': length,
        'filesize_to_show': "...",
        'format': format_text,
        'type': type
    }

    on_download_clicked(None, self, url, download_name, None, "playlist", video_options, False, self.appconf["download_directory"], 0)