import json
import os
import threading
import time

import xbmc
import xbmcgui
import xbmcvfs

from lib.download_manager import DownloadManager
from lib.downloader import (
    Downloader,
    cancel_flag_cache,
    get_download_metadata,
    handle_pause_download,
)
from lib.gui.base_window import BaseWindow
from lib.utils.kodi.settings import get_setting as _get_setting
from lib.utils.kodi.utils import (
    bytes_to_human_readable,
    execute_builtin,
    kodilog,
    translation,
)
from lib.utils.kodi.utils import open_file as _open_file
from lib.utils.kodi.utils import translatePath as _translatePath


class DownloadManagerWindow(BaseWindow):
    def __init__(self, xml_file, location, item_information=None, previous_window=None):
        super().__init__(xml_file, location, item_information, previous_window)
        self._monitor = xbmc.Monitor()
        self._closed = False
        self._poll_thread = None
        self._focused_item_id = None
        self._last_registry_checksum = ""

    def onInit(self):
        self.setProperty("instant_close", "false")
        self._sync_from_disk()
        self._rebuild_list()
        self._poll_thread = threading.Thread(target=self._poll_loop)
        self._poll_thread.daemon = True
        self._poll_thread.start()

    def _sync_from_disk(self):
        """Scan the download directory (recursively) for .jacktook.json files
        and register or update any downloads in the registry. This ensures
        that downloads started before the window was opened, or organized
        into subfolders, appear in the manager.
        """  # noqa: D205
        download_dir = _translatePath(_get_setting("download_dir"))

        if not download_dir or not xbmcvfs.exists(download_dir):
            kodilog(f"[DownloadManagerWindow] Download directory not found: {download_dir}")
            return

        manager = DownloadManager()
        try:
            self._walk_and_sync(download_dir, manager)
        except Exception as e:
            kodilog(f"[DownloadManagerWindow] Sync from disk error: {e}")

    def _walk_and_sync(self, directory, manager):
        """Recursively walk a directory using xbmcvfs and sync .jacktook.json files."""
        dirs, files = xbmcvfs.listdir(directory)

        for filename in files:
            filename = filename.decode("utf-8") if isinstance(filename, bytes) else filename
            if not filename.endswith(".jacktook.json"):
                continue
            dest_path = os.path.join(directory, filename.replace(".jacktook.json", ""))
            # Skip .part files — use the final path
            if dest_path.endswith(".part"):
                dest_path = dest_path[:-5]
            meta = get_download_metadata(dest_path)
            if not meta:
                continue
            status = meta.get("status", "unknown")
            progress = meta.get("progress", 0)
            url = meta.get("url", "")
            name = meta.get("title", os.path.basename(dest_path))
            # Only register non-cancelled entries
            if status == "cancelled":
                continue
            entry = manager.get_entry(dest_path)
            if entry:
                # Update existing entry from disk
                manager.set_status(dest_path, status)
                manager.update_progress(
                    dest_path,
                    downloaded=meta.get("downloaded", 0),
                    speed=meta.get("speed", 0),
                    eta=meta.get("eta", 0),
                    progress=progress,
                    size=meta.get("size", 0),
                )
            else:
                entry = manager.register(
                    name=name,
                    dest_path=dest_path,
                    url=url,
                )
                if entry:
                    manager.set_status(dest_path, status)
                    manager.update_progress(
                        dest_path,
                        downloaded=meta.get("downloaded", 0),
                        speed=meta.get("speed", 0),
                        eta=meta.get("eta", 0),
                        progress=progress,
                        size=meta.get("size", 0),
                    )
        for dirname in dirs:
            dirname = dirname.decode("utf-8") if isinstance(dirname, bytes) else dirname
            subdir = os.path.join(directory, dirname)
            self._walk_and_sync(subdir, manager)

    def _poll_loop(self):
        last_sync = 0
        while not self._monitor.abortRequested() and not self._closed:
            xbmc.sleep(500)
            try:
                now = time.time()
                if now - last_sync >= 2:
                    self._sync_from_disk()
                    last_sync = now
                self._update_selected_properties()
                self._check_registry_changes()
            except Exception as e:
                kodilog(f"[DownloadManagerWindow] Poll error: {e}")

    def _update_selected_properties(self):
        # Always sync focused_item_id from the list's current selection
        # so the details panel reflects keyboard/gamepad navigation too
        try:
            control_list = self.getControl(14001)
            item = control_list.getSelectedItem()
            if item:
                self._focused_item_id = item.getProperty("entry_id") or self._focused_item_id
        except Exception:
            pass

        manager = DownloadManager()
        entry = manager.get_entry(self._focused_item_id) if self._focused_item_id else None
        if entry:
            self.setProperty("selected_progress", str(entry.progress))
            self.setProperty(
                "selected_speed",
                bytes_to_human_readable(entry.speed) + "/s" if entry.speed else "",
            )
            eta_secs = entry.eta
            if eta_secs and eta_secs > 0:
                eta_mins, eta_secs = divmod(eta_secs, 60)
                eta_hrs, eta_mins = divmod(eta_mins, 60)
                if eta_hrs:
                    self.setProperty("selected_eta", f"{eta_hrs}h {eta_mins}m")
                elif eta_mins:
                    self.setProperty("selected_eta", f"{eta_mins}m {eta_secs}s")
                else:
                    self.setProperty("selected_eta", f"{eta_secs}s")
            else:
                self.setProperty("selected_eta", "")
            self.setProperty(
                "selected_size",
                bytes_to_human_readable(entry.size) if entry.size else "",
            )
            self.setProperty(
                "selected_downloaded",
                bytes_to_human_readable(entry.downloaded) if entry.downloaded else "",
            )
            self.setProperty("selected_status", entry.status)
            self.setProperty("selected_name", entry.name)
        else:
            self.setProperty("selected_progress", "")
            self.setProperty("selected_speed", "")
            self.setProperty("selected_eta", "")
            self.setProperty("selected_size", "")
            self.setProperty("selected_downloaded", "")
            self.setProperty("selected_status", "")
            self.setProperty("selected_name", "")

    def _check_registry_changes(self):
        manager = DownloadManager()
        entries = manager.list_entries()
        checksum = ",".join(f"{e.id}:{e.status}:{e.progress}" for e in entries)
        if checksum != self._last_registry_checksum:
            self._last_registry_checksum = checksum
            execute_builtin("SendClick(14000,14002)")

    def _rebuild_list(self):
        try:
            manager = DownloadManager()
            entries = manager.list_entries()
            control_list = self.getControl(14001)
            selected_position = control_list.getSelectedPosition()
            control_list.reset()

            if not entries:
                self.setProperty("downloads_empty", "true")
                return

            self.setProperty("downloads_empty", "false")
            for entry in entries:
                label = entry.name
                speed_str = bytes_to_human_readable(entry.speed) + "/s" if entry.speed else ""
                if entry.eta and entry.eta > 0:
                    eta_mins, eta_secs = divmod(entry.eta, 60)
                    eta_hrs, eta_mins = divmod(eta_mins, 60)
                    if eta_hrs:
                        eta_str = f"{eta_hrs}h {eta_mins}m"
                    elif eta_mins:
                        eta_str = f"{eta_mins}m {eta_secs}s"
                    else:
                        eta_str = f"{eta_secs}s"
                else:
                    eta_str = ""
                size_str = bytes_to_human_readable(entry.size) if entry.size else ""
                downloaded_str = (
                    bytes_to_human_readable(entry.downloaded) if entry.downloaded else ""
                )

                # Build secondary info line
                parts = []
                if entry.status == "completed":
                    translation(90811)  # "Completed"
                elif speed_str:
                    parts.append(speed_str)
                if eta_str and entry.status != "completed":
                    parts.append(eta_str)
                if downloaded_str and size_str:
                    parts.append(f"{downloaded_str} / {size_str}")
                elif size_str:
                    parts.append(size_str)

                info_line = " • ".join(parts)

                item = xbmcgui.ListItem(label=label)
                item.setProperty("entry_id", entry.id)
                item.setProperty("entry_name", entry.name)
                item.setProperty("entry_status", entry.status)
                item.setProperty("entry_progress", str(entry.progress))
                item.setProperty("entry_speed", speed_str)
                item.setProperty("entry_eta", eta_str)
                item.setProperty("entry_size", size_str)
                item.setProperty("entry_downloaded", downloaded_str)
                item.setProperty("entry_info", info_line)
                control_list.addItem(item)

            if 0 <= selected_position < control_list.size():
                control_list.selectItem(selected_position)

            # Set focus on the list so navigation works immediately
            self.setFocus(control_list)

            # Default focus to first entry if no previous selection
            if self._focused_item_id is None and entries:
                self._focused_item_id = entries[0].id
        except Exception as e:
            kodilog(f"[DownloadManagerWindow] Rebuild list error: {e}")

    def onAction(self, action):
        action_id = action.getId()
        if action_id in self.action_exitkeys_id:
            self._closed = True
        super().onAction(action)

    def onClick(self, control_id):
        if control_id == 14002:
            self._rebuild_list()
            return

        if control_id == 14008:
            self._closed = True
            self.close()
            return

        if control_id == 14007:
            self._clear_completed()
            execute_builtin("SendClick(14000,14002)")
            return

        if control_id not in (14003, 14004, 14005, 14006):
            return

        try:
            control_list = self.getControl(14001)
            item = control_list.getSelectedItem()
            if not item:
                return
            entry_id = item.getProperty("entry_id")
            if not entry_id:
                return
        except Exception:
            return

        self._focused_item_id = entry_id

        if control_id == 14003:
            self._pause_download(entry_id)
        elif control_id == 14004:
            self._resume_download(entry_id)
        elif control_id == 14005:
            self._cancel_download(entry_id)
        elif control_id == 14006:
            self._delete_download(entry_id)

        execute_builtin("SendClick(14000,14002)")

    def _pause_download(self, entry_id):
        manager = DownloadManager()
        entry = manager.get_entry(entry_id)
        if entry and entry.status == "downloading":
            entry.cancel_flag = True
            manager.set_status(entry_id, "paused")
            import json

            handle_pause_download({"file_path": json.dumps(entry_id)}, refresh=False)

    def _resume_download(self, entry_id):
        manager = DownloadManager()
        entry = manager.get_entry(entry_id)
        if entry and entry.status == "paused":
            import os

            dest_path = entry_id
            # Clear the cancel flag left by the pause operation
            cancel_flag_cache.set(dest_path, False)
            entry.cancel_flag = False
            meta = get_download_metadata(dest_path)
            manager.set_status(entry_id, "downloading")
            manager.update_progress(
                entry_id,
                downloaded=meta.get("downloaded", 0),
                speed=0,
                eta=0,
                progress=meta.get("progress", 0),
                size=meta.get("size", 0),
            )
            downloader = Downloader(
                url=meta.get("url", entry.url),
                destination=os.path.dirname(dest_path),
                name=os.path.basename(dest_path),
                registry_id=dest_path,
                show_progress=False,
            )
            downloader.run()

    def _cancel_download(self, entry_id):
        manager = DownloadManager()
        entry = manager.get_entry(entry_id)
        if not entry:
            return

        # Cancel works for both downloading and paused states
        entry.cancel_flag = True
        cancel_flag_cache.set(entry_id, True)
        manager.set_status(entry_id, "cancelled")

        # Update metadata on disk so a cancelled download doesn't resume on restart
        meta_path = entry_id + ".jacktook.json"
        if xbmcvfs.exists(meta_path):
            try:
                with _open_file(meta_path, "r") as f:
                    meta = json.load(f)
                meta["status"] = "cancelled"
                with _open_file(meta_path, "w") as f:
                    json.dump(meta, f)
            except Exception:
                pass

        # Delete the .part file (partial download) to free disk space
        temp_path = entry_id + ".part"
        if xbmcvfs.exists(temp_path):
            xbmcvfs.delete(temp_path)

    def _delete_download(self, entry_id):
        manager = DownloadManager()
        entry = manager.get_entry(entry_id)
        if not entry:
            return

        dialog = xbmcgui.Dialog()
        if not dialog.yesno(translation(90809), translation(90810)):
            return

        # Delete files
        if xbmcvfs.exists(entry_id):
            xbmcvfs.delete(entry_id)
        temp_path = entry_id + ".part"
        if xbmcvfs.exists(temp_path):
            xbmcvfs.delete(temp_path)
        meta_path = entry_id + ".jacktook.json"
        if xbmcvfs.exists(meta_path):
            xbmcvfs.delete(meta_path)

        manager.remove_entry(entry_id)

    def _clear_completed(self):
        manager = DownloadManager()
        for entry in list(manager.list_entries()):
            if entry.status == "completed":
                manager.remove_entry(entry.id)

    def handle_action(self, action_id, control_id=None):
        pass
