"""isard-py — SAML 2.0 + Azure Entra ID authentication for Gencat educational platform."""

from __future__ import annotations

from .auth import AuthFlow
from .client import GencatClient
from .errors import (
    AzureAuthError,
    AzureFormParsingError,
    AzureRedirectError,
    BookingRequiredError,
    HttpError,
    IsardError,
    NetworkError,
    RateLimitError,
    SamlAssertionError,
    SamlParseError,
    SamlValidationError,
    SessionExpiredError,
    TooManyRedirectsError,
    WafBlockedError,
)
from .installer import (
    InstallerError,
    UnsupportedPlatformError,
    install_virt_viewer,
    check_virt_viewer_status,
    launch_virt_viewer,
)
from .isardvdi import IsardVdiClient
from .types import (
    AuthSession,
    BastionHttp,
    BastionSsh,
    BastionTarget,
    Desktop,
    GencatConfig,
    IsardVdiSession,
    SamlAssertion,
    SamlResponse,
    SamlStatus,
    Template,
    UserInfo,
)

__all__ = [
    # Public API
    "login_to_gencat",
    "get_desktops",
    "create_desktop",
    "delete_desktop",
    "delete_desktop_permanent",
    "get_templates",
    "get_spice_file",
    "stop_desktop",
    "start_desktop",
    "star_desktop",
    "wait_desktop",
    "get_bastion",
    "set_bastion",
    "add_ssh_key",
    # Installer functions
    "install_virt_viewer",
    "check_virt_viewer_status",
    "launch_virt_viewer",
    # Clients
    "IsardVdiClient",
    # Types
    "AuthSession",
    "BastionHttp",
    "BastionSsh",
    "BastionTarget",
    "Desktop",
    "GencatConfig",
    "IsardVdiSession",
    "Template",
    "UserInfo",
    "SamlAssertion",
    "SamlResponse",
    "SamlStatus",
    # Errors
    "IsardError",
    "NetworkError",
    "HttpError",
    "BookingRequiredError",
    "WafBlockedError",
    "RateLimitError",
    "AzureAuthError",
    "AzureFormParsingError",
    "AzureRedirectError",
    "SamlValidationError",
    "SamlAssertionError",
    "SamlParseError",
    "SessionExpiredError",
    "TooManyRedirectsError",
    "InstallerError",
    "UnsupportedPlatformError",
]


def login_to_gencat(
    username: str, password: str, config: GencatConfig | None = None
) -> AuthSession:
    """Authenticate against the Gencat educational platform using SAML 2.0 via Azure Entra ID.

    Args:
        username: The user's principal name, e.g. ``user@edu.gencat.cat``.
        password: The user's password.
        config: Optional :class:`GencatConfig` to override defaults.

    Returns:
        An :class:`AuthSession` with user info and session expiry.

    Raises:
        :class:`AzureAuthError`: Invalid credentials or Azure auth failure.
        :class:`SamlValidationError`: SAML assertion validation failure.
        :class:`NetworkError`: Network-level connectivity error.
    """
    if config is None:
        config = GencatConfig()
    with GencatClient(config) as client:
        flow = AuthFlow(client, config)
        return flow.authenticate(username, password)


def get_desktops(
    session: IsardVdiSession,
    name: str | None = None,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> list[Desktop]:
    """Return all IsardVDI desktops for the authenticated user.

    Args:
        session: A valid :class:`IsardVdiSession` obtained from :meth:`IsardVdiClient.login`.
        name: If provided, filter to the single desktop with this name.
        base_url: IsardVDI base URL (defaults to the Gencat production instance).

    Returns:
        A list of :class:`Desktop` objects sorted by name.

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.get_desktops(name=name)


def create_desktop(
    session: IsardVdiSession,
    template_id: str,
    name: str,
    description: str | None = None,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> Desktop:
    """Create a new desktop from a template.

    Args:
        session: A valid :class:`IsardVdiSession`.
        template_id: The template UUID to create desktop from.
        name: Name for the new desktop.
        description: Optional description for the desktop.
        base_url: IsardVDI base URL.

    Returns:
        A :class:`Desktop` object representing the newly created desktop.

    Raises:
        :class:`HttpError`: Non-2xx response from the API (e.g., template not found, quota exceeded).
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.create_desktop(template_id, name, description)


def delete_desktop(
    session: IsardVdiSession,
    desktop_id: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> None:
    """Move a desktop to trash.

    Note: This moves the desktop to IsardVDI's trash. For permanent deletion,
    you'll need to confirm the deletion via the IsardVDI web interface.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to move to trash.
        base_url: IsardVDI base URL.

    Raises:
        :class:`HttpError`: Non-2xx response from the API (e.g., desktop not found, cannot move running desktop).
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        client.delete_desktop(desktop_id)


def delete_desktop_permanent(
    session: IsardVdiSession,
    desktop_id: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> None:
    """Permanently delete a desktop (cannot be undone).

    WARNING: This operation is irreversible. The desktop and all its data will be
    permanently lost and cannot be recovered from trash or any other means.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to permanently delete.
        base_url: IsardVDI base URL.

    Raises:
        :class:`HttpError`: Non-2xx response from the API (e.g., desktop not found, cannot delete running desktop).
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        client.delete_desktop_permanent(desktop_id)


def stop_desktop(
    session: IsardVdiSession,
    desktop_id: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> str:
    """Stop a running desktop.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to stop.
        base_url: IsardVDI base URL.

    Returns:
        The new desktop state string (e.g. ``"Stopping"``).

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.stop_desktop(desktop_id)


def start_desktop(
    session: IsardVdiSession,
    desktop_id: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> str:
    """Start a stopped desktop.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to start.
        base_url: IsardVDI base URL.

    Returns:
        The new desktop state string (e.g. ``"Starting"``).

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.start_desktop(desktop_id)


def star_desktop(
    session: IsardVdiSession,
    desktop_id: str,
    starred: bool = True,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> None:
    """Mark (or unmark) a desktop as a favourite / starred.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to star or unstar.
        starred: ``True`` to star, ``False`` to unstar.
        base_url: IsardVDI base URL.

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        client.star_desktop(desktop_id, starred=starred)


def wait_desktop(
    session: IsardVdiSession,
    desktop_id: str,
    target_state: str,
    timeout: float = 120.0,
    poll_interval: float = 3.0,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> str:
    """Poll until a desktop reaches *target_state* or the timeout expires.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to poll.
        target_state: The state to wait for, e.g. ``"Started"`` or ``"Stopped"``.
        timeout: Maximum seconds to wait (default 120).
        poll_interval: Seconds between polls (default 3).
        base_url: IsardVDI base URL.

    Returns:
        The final desktop state string.

    Raises:
        :class:`TimeoutError`: Desktop did not reach *target_state* in time.
        :class:`HttpError`: Non-2xx response from the API during polling.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.wait_for_state(
            desktop_id,
            target_state,
            timeout=timeout,
            poll_interval=poll_interval,
        )


def get_bastion(
    session: IsardVdiSession,
    desktop_id: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> "BastionTarget":
    """Return the current bastion (SSH jump-host) config for a desktop.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID.
        base_url: IsardVDI base URL.

    Returns:
        A :class:`BastionTarget` with the current SSH/HTTP settings.

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.get_bastion(desktop_id)


def set_bastion(
    session: IsardVdiSession,
    desktop_id: str,
    *,
    ssh_enabled: bool = True,
    ssh_port: int = 22,
    authorized_keys: list[str] | None = None,
    http_enabled: bool = False,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> "BastionTarget":
    """Enable or update the SSH bastion for a desktop.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID.
        ssh_enabled: Whether SSH bastion access should be active.
        ssh_port: The VM-side SSH port (default 22).
        authorized_keys: If provided, replaces the full key list. ``None`` keeps existing.
        http_enabled: Whether HTTP proxying should be active.
        base_url: IsardVDI base URL.

    Returns:
        The updated :class:`BastionTarget`.

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.set_bastion(
            desktop_id,
            ssh_enabled=ssh_enabled,
            ssh_port=ssh_port,
            authorized_keys=authorized_keys,
            http_enabled=http_enabled,
        )


def add_ssh_key(
    session: IsardVdiSession,
    desktop_id: str,
    public_key: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> "BastionTarget":
    """Add a single SSH public key to a desktop's authorized_keys list.

    Idempotent — if the key is already present it is not duplicated.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID.
        public_key: The full public-key string, e.g. ``"ssh-ed25519 AAAA... user@host"``.
        base_url: IsardVDI base URL.

    Returns:
        The updated :class:`BastionTarget`.

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.add_ssh_key(desktop_id, public_key)


def get_templates(
    session: IsardVdiSession,
    category_id: str | None = None,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> list[Template]:
    """Return available IsardVDI templates/images for the authenticated user.

    Args:
        session: A valid :class:`IsardVdiSession`.
        category_id: Optional category UUID to filter templates. If not provided,
            uses the default Gencat educational category.
        base_url: IsardVDI base URL.

    Returns:
        A list of :class:`Template` objects sorted by name.

    Raises:
        :class:`HttpError`: Non-2xx response from the API.
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.get_templates(category_id=category_id)


def get_spice_file(
    session: IsardVdiSession,
    desktop_id: str,
    base_url: str = "https://elmeuescriptori.gestioeducativa.gencat.cat",
) -> str:
    """Get the SPICE connection file for a desktop.

    Args:
        session: A valid :class:`IsardVdiSession`.
        desktop_id: The desktop UUID to get SPICE file for.
        base_url: IsardVDI base URL.

    Returns:
        The SPICE file content as a string.

    Raises:
        :class:`HttpError`: Non-2xx response from the API (e.g., desktop not found, not running).
        :class:`NetworkError`: Network-level connectivity error.
    """
    with IsardVdiClient(session, base_url=base_url) as client:
        return client.get_spice_file(desktop_id)
