from sam.managers import Manager
from sam.actions import Action
from swai_inst.exceptions import SwaiError
import swai_inst.installer as installer
import swai_inst.updater as updater
import swai_inst.utils as inst_utils
from typing import Optional, Tuple, List
import os
import yaml

class SwaiManager(Manager):
    title: str
    manager_id: str
    current_action = Action

    @property
    def manager_type(self) -> str:
        return "swai"

    # Packages will be formated like {app_id},{swai_url}
    # We need to parse the package_id and swai_url
    def parse_package_id(self, package_id: str) -> Tuple[str, Optional[str]]:
        parts = package_id.split(',')
        app_id = parts[0]
        swai_url = ','.join(parts[1:])        # Done incase url has commas
        if swai_url == "null":
            swai_url = None

        return app_id, swai_url
    
    def format_package_id(self, app_id: str, swai_url: Optional[str]) -> str:
        if swai_url is None:
            swai_url = "null"
        return f"{app_id},{swai_url}"

    def app_id_matches(self, expected_app_id: str, candidate_app_id: Optional[str]) -> bool:
        if candidate_app_id is None:
            return False
        return candidate_app_id == expected_app_id or candidate_app_id == f"io.stillhq.{expected_app_id}"

    def get_swai_apps_dir(self) -> str:
        swai_dir, _, _ = inst_utils.get_install_paths()
        return swai_dir

    def load_swai_data(self, path: str) -> Optional[dict]:
        resolved_path = os.path.realpath(path)
        if not os.path.isfile(resolved_path):
            return None

        with open(resolved_path, "rb") as swai_file:
            swai_bytes = swai_file.read()

        for encoding in ("utf-8", "utf-8-sig"):
            try:
                swai_data = yaml.safe_load(swai_bytes.decode(encoding))
                if isinstance(swai_data, dict):
                    return swai_data
                return None
            except UnicodeDecodeError:
                continue
            except yaml.YAMLError:
                return None
        return None
    
    def find_app_path_from_app_id(self, app_id: str) -> str:
        swai_apps_dir = self.get_swai_apps_dir()
        if not os.path.isdir(swai_apps_dir):
            return None

        for swai_file in os.listdir(swai_apps_dir):
            swai_data = self.load_swai_data(os.path.join(swai_apps_dir, swai_file))
            if swai_data is None:
                continue
            if self.app_id_matches(app_id, swai_data.get("app_id")):
                return os.path.join(swai_apps_dir, swai_file)
        return None

    def get_package_id_from_app_id(self, app_id: str) -> str:
        app_path = self.find_app_path_from_app_id(app_id)
        if app_path is not None:
            swai_data = self.load_swai_data(app_path)
            if swai_data is not None:
                return self.format_package_id(app_id, swai_data.get("update_url"))
        return None

    def get_app_id(self, package_id: str) -> str:
        return package_id.split(',')[0]
    
    def get_swai_url(self, package_id: str) -> str:
        return package_id.split(',')[1]

    def install(self, action: Action):
        action.progress = 0
        try:
            _, swai_url = self.parse_package_id(action.package_id)
            installer.install_from_url(swai_url)
        except SwaiError as e:
            action.error = str(e)
        action.progress = 100
        action.status = "Installed"

    def remove(self, action: Action):
        action.progress = 0
        try:
            app_id, _ = self.parse_package_id(action.package_id)
            installer.remove_from_app_id(app_id)
        except SwaiError as e:
            action.error = str(e)
        action.progress = 99

    def update(self, action: Action):
        action.progress = 0
        try:
            app_id, _ = self.parse_package_id(action.package_id)
            updater.update_app_from_app_id(app_id)
        except SwaiError as e:
            action.error = str(e)
        action.progress = 99

    def check_update(self, package_id: str) -> bool:
        app_id, _ = self.parse_package_id(package_id)
        return updater.check_for_update(app_id)

    def check_updates(self) -> List[str]:
        updates = []
        # This function only returns the app ids
        # so we need to get the update urls
        update_app_ids = updater.check_for_updates()

        # Get update url for each app to put in the id
        for update in update_app_ids:
            update_package_id = self.get_package_id_from_app_id(update)
            if update_package_id is not None:
                updates.append(update_package_id)

        return updates

    def check_installed(self) -> List[str]:
        installed_apps = []
        for app_id in inst_utils.get_installed_app_ids():
            package_id = self.get_package_id_from_app_id(app_id)
            if package_id is not None:
                installed_apps.append(package_id)
        return installed_apps

    def bare_app_info(self, package_id: str) -> dict:
        app_id, _ = self.parse_package_id(package_id)
        app_path = self.find_app_path_from_app_id(app_id)
        if app_path is not None:
            swai_data = self.load_swai_data(app_path)
            if swai_data is not None:
                return {
                    "app_id": f"swai-{app_id.replace(".", "-")}",
                    "name": swai_data.get("app_name"),
                    "author": "Unknown",
                    "primary_src": self.manager_id,
                    "src_package_name": package_id,
                    "icon_url": swai_data.get("icon_url"),
                    "summary": swai_data.get("summary", "No summary available"),
                    "description": swai_data.get("description", "No description available"),
                }

