import os
import shutil
from typing import Optional
from urllib.parse import urlparse, unquote

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Gdk", "4.0")
gi.require_version("Soup", "3.0")
gi.require_version("Adw", "1")

from gi.repository import Gtk, Gdk, Soup, GLib, GObject, Adw

import stillCenter.NetworkErrors as NetworkErrors
import stillCenter.constants as constants

# Set cache location to ~/.cache/stillcenter
_CACHE_DIR = os.path.join(os.path.expanduser("~"), ".cache/stillcenter")
_ICON_DIR = os.path.join(_CACHE_DIR, "icons")
_SCREENSHOT_DIR = os.path.join(_CACHE_DIR, "screenshots")

for dir in [_CACHE_DIR, _ICON_DIR, _SCREENSHOT_DIR]:
    if not os.path.exists(dir):
        os.makedirs(dir)

# Clear out screenshot dir
shutil.rmtree(_SCREENSHOT_DIR)
os.makedirs(_SCREENSHOT_DIR)

_NETWORK_SOUP_STATUSES = tuple(
    status for status in (
        getattr(Soup.Status, "CANT_RESOLVE", None),
        getattr(Soup.Status, "CANT_RESOLVE_PROXY", None),
        getattr(Soup.Status, "CANT_CONNECT", None),
        getattr(Soup.Status, "CANT_CONNECT_PROXY", None),
        getattr(Soup.Status, "SSL_FAILED", None),
        getattr(Soup.Status, "IO_ERROR", None),
    ) if status is not None
)

@Gtk.Template(filename=os.path.join(constants.UI_DIR, "UrlImage.ui"))
class UrlImage(Adw.Bin):
    __gtype_name__ = "UrlImage"
    _screenshot: bool = False
    _image_url: str = ""
    _app_id: str = "app"
    image_dir = _ICON_DIR
    image_path = ""
    image: Gtk.Picture = Gtk.Template.Child()

    def __init__(self):
        super().__init__()
        self._picture = self.image
        self._status_page = Adw.StatusPage()
        self._status_page.set_vexpand(True)
        self._status_page.set_hexpand(True)
        self._status_page.set_icon_name("image-missing-symbolic")
        self._status_page.set_title("Unable to load image")
        self._status_page.set_description("")

    def show_picture(self):
        if self.get_child() is not self._picture:
            self.set_child(self._picture)

    def show_screenshot_error(self, title: str, description: str, icon_name: str = "image-missing-symbolic"):
        self._status_page.set_icon_name(icon_name)
        self._status_page.set_title(title)
        self._status_page.set_description(description)
        if self.get_child() is not self._status_page:
            self.set_child(self._status_page)

    @GObject.Property(type=bool, default=False)
    def screenshot(self):
        return self._screenshot

    @screenshot.setter
    def screenshot(self, value):
        self._screenshot = value
        if value:
            self.image_dir = _SCREENSHOT_DIR
            # Fit screenshots within the display area without cropping.
            self.image.set_content_fit(Gtk.ContentFit.CONTAIN)
        else:
            self.image_dir = _ICON_DIR
            self.image.set_content_fit(Gtk.ContentFit.CONTAIN)

    @GObject.Property(type=str)
    def app_id(self):
        return self._app_id

    @app_id.setter
    def app_id(self, value):
        self._app_id = value

        self.set_image_url(value, self._image_url)

    @GObject.Property(type=str)
    def image_url(self):
        return self._image_url

    @image_url.setter
    def image_url(self, value):
        self._image_url = value
        self.set_image_url(self._app_id, value)

    def clear_image(self, icon_name="image-missing-symbolic"):
        self.show_picture()
        self.image.set_paintable(
            Gtk.IconTheme.get_for_display(
                Gdk.Display.get_default()
            ).lookup_icon(
                icon_name, [],
                64, 1, 1, Gtk.TextDirection.NONE
            )
        )

    def on_receive_bytes(self, session, result, message):
        try:
            bytes = session.send_and_read_finish(result)
        except GLib.Error as error:
            if self.screenshot:
                if NetworkErrors.is_connection_error(error):
                    self.show_screenshot_error(
                        "Unable to load screenshot",
                        NetworkErrors.build_connection_error_description("this screenshot", error),
                        "network-error-symbolic"
                    )
                else:
                    self.show_screenshot_error(
                        "Unable to load screenshot",
                        "This screenshot could not be loaded right now."
                    )
            elif NetworkErrors.is_connection_error(error):
                self.clear_image("network-error-symbolic")
            else:
                self.clear_image()
            return

        if message.get_status() != Soup.Status.OK:
            if self.screenshot:
                if message.get_status() in _NETWORK_SOUP_STATUSES:
                    self.show_screenshot_error(
                        "Unable to load screenshot",
                        "Connect to the internet and try again to load this screenshot.",
                        "network-error-symbolic"
                    )
                else:
                    self.show_screenshot_error(
                        "Unable to load screenshot",
                        f"This screenshot could not be loaded.\n\nStatus: {message.get_status()}"
                    )
            elif message.get_status() in _NETWORK_SOUP_STATUSES:
                self.clear_image("network-error-symbolic")
            else:
                self.clear_image()
            return

        try:
            self.show_picture()
            texture = Gdk.Texture.new_from_bytes(bytes)
            self.image.set_paintable(texture)
            with open(self.image_path, "wb") as f:
                f.write(bytes.get_data())
        except GLib.Error:
            self.clear_image()

    def set_image_url(self, app_id, url):
        self.image_path = get_file_name_from_url(url, app_id, self.image_dir)

        if self.image_path is None:
            self.clear_image()
            return

        if os.path.exists(self.image_path):
            try:
                self.show_picture()
                self.image.set_filename(self.image_path)
                return
            except GLib.Error:
                # Remove corrupt cache files and re-download.
                os.remove(self.image_path)

        session = Soup.Session()
        try:
            message = Soup.Message(
                method="GET",
                uri=GLib.Uri.parse(url, GLib.UriFlags.NONE)
            )
        except GLib.GError:
            if self.screenshot:
                self.show_screenshot_error(
                    "Unable to load screenshot",
                    "This screenshot could not be loaded right now."
                )
            else:
                self.clear_image()
            return

        session.send_and_read_async(
            message, GLib.PRIORITY_DEFAULT, None, self.on_receive_bytes, message
        )


def get_file_name_from_url(url: Optional[str], app_id: str, base_dir: str) -> Optional[str]:
    if url == "" or url is None:
        return None
    if os.path.exists(url):
        return url

    # Parse the url to get the path
    path = urlparse(url).path

    # Get the file name from the path
    try:
        filename = unquote(path.split('/')[-1])
    except TypeError:
        return None

    return os.path.join(base_dir, f"{app_id}-{filename}")