"""
Contains code for updating SWAI applications with the .swai format.
Also handles .swaib bundle updates.
"""

import os
from typing import List, Tuple, Optional
import yaml
import swai_inst.utils as utils
import swai_inst.installer as installer
from swai_inst.exceptions import SwaiUpdateError, SwaiErrorCodes, SwaiBundleError

def check_for_update(app_id: str, local_install: bool = False, verbose: bool = False) -> Tuple[bool, Optional[str], Optional[str], Optional[str]]:
    """
    Checks if a single SWAI application has an update available.

    Args:
        app_id (str): The app ID to check for updates.
        local_install (bool, optional): Whether to use user-local installation paths. Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.
        
    Returns:
        Tuple[bool, Optional[str], Optional[str], Optional[str]]: 
            - bool: True if update is available, False otherwise
            - Optional[str]: Current version (None if not found)
            - Optional[str]: Latest version (None if not found/error)
            - Optional[str]: Latest SWAI content (None if no update or error)
        
    Raises:
        SwaiUpdateError: If there are critical issues checking for updates.
    """
    app_path = utils.get_app_manifest_path(app_id, local_install)
    
    # Load yaml file from .swai file
    try:
        app_data = installer._parse_swai_file(app_path)
    except Exception as e:
        if verbose:
            print(f"Error parsing SWAI file for app '{app_id}': {e}")
        return False, None, None, None
        
    update_url = app_data.get('update_url')
    current_version = app_data.get('swai_file_version')
    
    if not update_url:
        # No update URL found, no update available
        return False, current_version, None, None
    
    if not current_version:
        # No version found, skip this app
        if verbose:
            print(f"No version found for app with update URL '{update_url}'. Skipping.")
        return False, None, None, None
        
    # Download remote .swai file and compare versions
    try:
        remote_swai_content = utils.download_swai_to_string(update_url)
        remote_swai_data = yaml.safe_load(remote_swai_content)
    except Exception as e:
        if verbose:
            print(f"Error getting remote .swai file for app with update URL '{update_url}': {e}")
        return False, current_version, None, None

    latest_version = remote_swai_data.get('swai_file_version')
    
    if latest_version and latest_version > current_version:
        return True, current_version, latest_version, remote_swai_content
    else:
        return False, current_version, latest_version, None

def check_for_updates(local_install=False, verbose=False) -> Tuple[List[str], List[str]]:
    """
    Checks for updates for all installed SWAI applications and bundles.

    Args:
        local_install (bool, optional): Whether to use user-local installation paths (i.e. for the current user only). Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.
        
    Returns:
        Tuple[List[str], List[str]]: (List of app IDs with updates, List of bundle IDs with updates)
        
    Raises:
        SwaiUpdateError: If there are issues checking for updates.
    """
    # Check individual apps
    app_ids = utils.get_installed_app_ids(local_install)
    app_updates_available = []

    for app_id in app_ids:
        has_update, _, _, _ = check_for_update(app_id, local_install, verbose)
        if has_update:
            app_updates_available.append(app_id)
    
    # Check bundles
    bundle_ids = utils.get_installed_bundle_ids(local_install)
    bundle_updates_available = []

    for bundle_id in bundle_ids:
        import swai_inst.bundle as bundle
        has_update, _, _, _ = bundle.check_bundle_for_update(bundle_id, local_install, verbose)
        if has_update:
            bundle_updates_available.append(bundle_id)
        
    return app_updates_available, bundle_updates_available


def update_app_from_file(app_path, local_install=False, verbose=False):
    """
    Updates the specified SWAI application.

    Args:
        app_path (str): The path to the .swai file to update.
        local_install (bool, optional): Whether to use user-local installation paths (i.e. for the current user only). Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.
        
    Returns:
        bool: True if the app was updated, False if it was already up to date.
        
    Raises:
        SwaiUpdateError: If there are issues during the update process.
    """
    
    # Extract app_id from the file path
    app_id = os.path.splitext(os.path.basename(app_path))[0]
    
    # Check for updates using the new helper function
    try:
        has_update, current_version, latest_version, latest_content = check_for_update(app_id, local_install, verbose)
    except Exception as e:
        raise SwaiUpdateError(
            f"Error checking for updates for app '{app_id}': {e}",
            app_id=app_id,
            error_code=SwaiErrorCodes.VERSION_CHECK_FAILED,
            original_exception=e
        )
    
    if not has_update:
        # Load the app data to check if there's no update URL (different from no update available)
        try:
            app_data = installer._parse_swai_file(app_path)
            update_url = app_data.get('update_url')
            if not update_url:
                raise SwaiUpdateError(
                    f"No update URL found for app with ID '{app_id}'.",
                    app_id=app_id,
                    error_code=SwaiErrorCodes.NO_UPDATE_URL
                )
        except SwaiUpdateError:
            raise
        except Exception as e:
            raise SwaiUpdateError(
                f"Error parsing SWAI file '{app_path}': {e}",
                error_code=SwaiErrorCodes.UPDATE_FAILED,
                original_exception=e
            )
        
        if verbose:
            print(f"App '{app_id}' is up to date.")
        return False
    
    # Update is available, perform the update
    if verbose:
        print(f"Updating app '{app_id}' from version '{current_version}' to version '{latest_version}'.")
    
    try:
        installer.remove_from_app_id(app_id, local_install=local_install, verbose=verbose, remove_data=False)
        installer.install_from_str(latest_content, local_install=local_install, verbose=verbose)
    except Exception as e:
        raise SwaiUpdateError(
            f"Error updating app '{app_id}': {e}",
            app_id=app_id,
            error_code=SwaiErrorCodes.UPDATE_FAILED,
            original_exception=e
        )
    return True


def update_app_from_app_id(app_id, local_install=False, verbose=False):
    """
    Updates the specified SWAI application by its app_id.

    Args:
        app_id (str): The app_id of the application to update.
        local_install (bool, optional): Whether to use user-local installation paths (i.e. for the current user only). Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.
        
    Returns:
        bool: True if the app was updated, False if it was already up to date.
        
    Raises:
        SwaiUpdateError: If there are issues during the update process.
    """
    swai_file_path = utils.get_app_manifest_path(app_id, local_install)
    
    # Update the app
    return update_app_from_file(swai_file_path, local_install=local_install, verbose=verbose)


def update_all(local_install=False, verbose=False):
    """
    Updates all installed SWAI applications and bundles.

    Args:
        local_install (bool, optional): Whether to use user-local installation paths (i.e. for the current user only). Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.
        
    Returns:
        int: Number of apps and bundles that were updated.
        
    Raises:
        SwaiUpdateError: If there are issues during the update process.
    """
    updated_count = 0
    
    # Update individual apps
    app_ids = utils.get_installed_app_ids(local_install=local_install)
    for app_id in app_ids:
        try:
            if update_app_from_app_id(app_id, local_install=local_install, verbose=verbose):
                updated_count += 1
        except SwaiUpdateError as e:
            if verbose:
                print(f"Error updating app '{app_id}': {e}")
            # Continue with other apps instead of stopping
            continue
    
    # Update bundles
    bundle_ids = utils.get_installed_bundle_ids(local_install=local_install)
    for bundle_id in bundle_ids:
        try:
            if update_bundle_from_id(bundle_id, local_install=local_install, verbose=verbose):
                updated_count += 1
        except (SwaiUpdateError, SwaiBundleError) as e:
            if verbose:
                print(f"Error updating bundle '{bundle_id}': {e}")
            continue
    
    return updated_count


def check_bundle_for_update(bundle_id: str, local_install: bool = False, verbose: bool = False) -> Tuple[bool, Optional[str], Optional[str], Optional[str]]:
    """
    Checks if a bundle has an update available.

    Args:
        bundle_id (str): The bundle ID to check.
        local_install (bool, optional): Whether to use local paths. Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.

    Returns:
        Tuple[bool, Optional[str], Optional[str], Optional[str]]:
            - bool: True if update is available, False otherwise
            - Optional[str]: Current version (None if not found)
            - Optional[str]: Latest version (None if not found/error)
            - Optional[str]: Latest SWAIB content (None if no update or error)
    """
    import swai_inst.bundle as bundle
    return bundle.check_bundle_for_update(bundle_id, local_install, verbose)


def update_bundle_from_id(bundle_id: str, local_install: bool = False, verbose: bool = False) -> bool:
    """
    Updates a bundle to the latest version.

    Args:
        bundle_id (str): The bundle ID to update.
        local_install (bool, optional): Whether to use local paths. Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.

    Returns:
        bool: True if the bundle was updated, False if already up to date.

    Raises:
        SwaiBundleError: If there are issues during the update process.
    """
    import swai_inst.bundle as bundle
    return bundle.update_bundle(bundle_id, local_install, verbose)


def check_for_bundle_updates(local_install: bool = False, verbose: bool = False) -> List[str]:
    """
    Checks for updates for all installed bundles.

    Args:
        local_install (bool, optional): Whether to use local paths. Defaults to False.
        verbose (bool, optional): Whether to print verbose output. Defaults to False.

    Returns:
        List[str]: List of bundle IDs that have updates available.
    """
    bundle_ids = utils.get_installed_bundle_ids(local_install)
    updates_available = []

    for bundle_id in bundle_ids:
        has_update, _, _, _ = check_bundle_for_update(bundle_id, local_install, verbose)
        if has_update:
            updates_available.append(bundle_id)

    return updates_available
