#!/usr/bin/env python
#
# Package Name: ewccli
# License: GPL-3.0-or-later
# Copyright (c) 2025 EUMETSAT, ECMWF for European Weather Cloud
# See the LICENSE file for more details


"""CLI EWC Login: EWC Login interaction."""

import os
import re
from pathlib import Path

import rich_click as click
from rich.console import Console
from click import ClickException

from prompt_toolkit.application import Application
from prompt_toolkit.widgets import RadioList, Box, Frame
from prompt_toolkit.layout import Layout
from prompt_toolkit.styles import Style

from kubernetes import config
from kubernetes.config.config_exception import (  # noqa: N813
    ConfigException as kubernetes_config_exception,
)
from openstack.config import OpenStackConfig
from openstack.exceptions import (  # noqa: N813
    ConfigException as openstack_config_exception,
)

from ewccli.configuration import config as ewc_hub_config
from ewccli.utils import save_cli_profile, _resolve_profile
from ewccli.utils import generate_ssh_keypair, ssh_keys_match
from ewccli.utils import save_default_login_profile
from ewccli.enums import Federee
from ewccli.logger import get_logger

_LOGGER = get_logger(__name__)


console = Console()


def kubeconfig_available():
    """Verify if kubeconfig is available."""
    try:
        config.load_kube_config()
        return True
    except kubernetes_config_exception as e:
        _LOGGER.warning(
            f"⚠️ Kubeconfig not found: {e}\n"
            "You could set KUBECONFIG=/path/to/your/kubeconfig or continue below using the token"
        )
        return False


def cloud_yaml_exists():
    """Check if OpenStack clouds.yaml file exists."""
    # Default OpenStack config paths (can vary by environment)
    default_paths = [
        Path(
            os.getenv("OS_CLIENT_CONFIG_FILE", "~/.config/openstack/clouds.yaml")
        ).expanduser(),
        Path("/etc/openstack/clouds.yaml"),
    ]

    return any(p.exists() for p in default_paths)


def openstack_config_available():
    """Verify if OpenStack cloud config is available."""
    try:
        os_config = OpenStackConfig()
        if cloud_yaml_exists():
            os_config.get_one_cloud()
        else:
            _LOGGER.warning(
                "⚠️ OpenStack cloud config not found at '~/.config/openstack/cloud.yaml'\n"
                "You can set the config path with the environment variable:\n"
                "  OS_CLIENT_CONFIG_FILE=/path/to/clouds.yaml\n"
                "Alternatively, provide your credentials using:\n"
                "  OS_APPLICATION_CREDENTIAL_ID and OS_APPLICATION_CREDENTIAL_SECRET\n"
                "Or continue below to enter them manually."
            )
            return False
        return True
    except openstack_config_exception as e:
        _LOGGER.warning(
            f"⚠️ OpenStack cloud config not found: {e}\n"
            "You can also set the config path with `OS_CLIENT_CONFIG_FILE=/path/to/clouds.yaml` or continue below"
        )
        return False


def validate_tenant_name(ctx, param, value):
    """Validate tenant name."""
    pattern = r"^[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+$"
    if not re.match(pattern, value):
        raise click.BadParameter(
            "Config name must be exactly 3 alphanumeric parts separated by dashes (e.g. thisis-my-tenancy)."
        )
    return value


def init_options(func):
    """Login options for the CLI login command."""
    func = click.option(
        "--tenant-name",
        envvar="EWC_CLI_LOGIN_TENANT_NAME",
        prompt=True,
        required=True,
        callback=validate_tenant_name,
        help=(
            "Name of your tenancy in EWC, used to identify cloud configurations.\n"
            "Must follow the format: 'part1-part2-part3' (e.g. 'demo-user-eu'), "
            "where each part is alphanumeric and separated by dashes.\n"
            "Can also be set via the EWC_CLI_LOGIN_TENANT_NAME environment variable."
        ),
    )(func)
    func = click.option(
        "--federee",
        type=click.Choice(
            [Federee.ECMWF.value, Federee.EUMETSAT.value], case_sensitive=True
        ),
        envvar="EWC_CLI_LOGIN_REGION",
        help=(
            "Cloud federee where the resources will be deployed. "
            "You can also set this using the EWC_CLI_LOGIN_REGION environment variable. "
            "If not provided, you'll be prompted to choose."
        ),
    )(func)
    func = click.option(
        "--application-credential-id",
        required=False,
        hide_input=True,
        help=(
            "OpenStack Application Credential ID. "
            "Ignored if environment variable OS_APPLICATION_CREDENTIAL_ID is set, "
            "or if a cloud.yaml config is found at '~/.config/openstack/cloud.yaml' "
            "or at the path specified by OS_CLIENT_CONFIG_FILE."
        ),
    )(func)
    func = click.option(
        "--application-credential-secret",
        required=False,
        hide_input=True,
        help=(
            "OpenStack Application Credential Secret. "
            "Ignored if environment variable OS_APPLICATION_CREDENTIAL_SECRET is set, "
            "or if a cloud.yaml config is found at '~/.config/openstack/cloud.yaml' "
            "or at the path specified by OS_CLIENT_CONFIG_FILE."
        ),
    )(func)
    # func = click.option(
    #     "--token",
    #     hide_input=True,
    #     required=False,
    #     default="",
    #     help=(
    #         "Kubernetes token (leave blank if not needed).\n"
    #         "Provide this only if you plan to use Kubernetes services and "
    #         "do not have a kubeconfig file available "
    #         "(e.g. ~/.kube/config or via the KUBECONFIG environment variable)."
    #     ),
    # )(func)
    func = click.option(
        "--ssh-public-key-path",
        required=False,
        envvar="EWC_CLI_SSH_PUBLIC_KEY_PATH",
        type=str,
        default=ewc_hub_config.EWC_CLI_PUBLIC_SSH_KEY_PATH,
        show_default=True,
        help="Path to SSH public key.",
    )(func)
    func = click.option(
        "--ssh-private-key-path",
        required=False,
        envvar="EWC_CLI_SSH_PRIVATE_KEY_PATH",
        type=str,
        default=ewc_hub_config.EWC_CLI_PRIVATE_SSH_KEY_PATH,
        show_default=True,
        help="Path to SSH private key.",
    )(func)
    func = click.option(
        "--profile",
        envvar="EWC_CLI_LOGIN_PROFILE",
        required=False,
        help="EWC CLI profile name",
    )(func)
    return func


def select_provider():
    """Select provider."""
    choices = [
        ("EUMETSAT", "EUMETSAT"),
        ("ECMWF", "ECMWF"),
    ]

    radio_list = RadioList(choices)

    # Use the widget's own default key bindings
    kb = radio_list.control.key_bindings

    @kb.add("enter")
    def _(event):
        index = radio_list._selected_index
        selected_value = radio_list.values[index][
            1
        ]  # values is list of tuples (display, value)
        event.app.exit(result=selected_value)

    # Add quit keys as well
    @kb.add("c-c")
    @kb.add("c-q")
    def _(event):
        event.app.exit(None)

    root_container = Box(Frame(radio_list, title="Select Federee"), padding=1)
    layout = Layout(root_container)

    app = Application(
        layout=layout,
        key_bindings=kb,
        full_screen=False,
        mouse_support=True,
        style=Style.from_dict(
            {
                "frame.label": "bold",
            }
        ),
    )

    selected = app.run()
    return selected


def check_and_generate_ssh_keys(
    ssh_public_key_path: str,
    ssh_private_key_path: str,
    resolved_profile: str
):
    """Check for SSH keys, prompt to generate if missing"""
    private_exists = Path(ssh_private_key_path).exists()
    public_exists = Path(ssh_public_key_path).exists()

    if private_exists and public_exists:
        # case 1: both exist
        console.print(
            "Using the following path for the SSH keypair:"
            f"\nSSH public key path: {ssh_public_key_path}"
            f"\nSSH private key path: {ssh_private_key_path}\n"
        )
        console.print("SSH key pair exists, checking consitency...")

        is_matching = ssh_keys_match(
            ssh_private_key_path=ssh_private_key_path,
            ssh_public_key_path=ssh_public_key_path
        )

        if not is_matching:
            raise ClickException(
                "SSH keys provided are not a correct keypair:"
                f"\nSSH public key path: {ssh_public_key_path}"
                f"\nSSH private key path: {ssh_private_key_path}"
                "\nMake sure either you pass correct SSH keypair in the EWC login command through the following flags `--ssh-private-key-path` and `--ssh-public-key-path`"
                "or let the `ewc login` command create them for you. Exiting."
            )
        else:
            click.secho("SSH private and public keys are matching! Continuing...", fg="green")
        
        return ssh_private_key_path, ssh_public_key_path

    elif not private_exists and not public_exists:
        # case 2: neither exists
        console.print(
            "SSH key pair is missing:"
            f"\nSSH public key path: {ssh_public_key_path}"
            f"\nSSH private key path: {ssh_private_key_path}\n"
        )

        if click.confirm("Do you want to generate a new SSH key pair?", default=False):
            ssh_custom_private_key_path, ssh_custom_public_key_path = generate_ssh_keypair(
                resolved_profile=resolved_profile
            )
            return ssh_custom_private_key_path, ssh_custom_public_key_path
        else:
            raise ClickException(
                "SSH key generation skipped but SSH keys are mandatory to deploy VMs or hub items."
                " Make sure either you pass SSH keys in the EWC login command through the following flags `--ssh-private-key-path` and `--ssh-public-key-path`"
                "or let the `ewc login` command create them for you. Exiting."
            )

    else:
        # case 3: exactly one exists
        if private_exists and not public_exists:
            key_exists = "public"
            key_path = ssh_public_key_path

        if not private_exists and public_exists:
            key_exists = "private"
            key_path = ssh_private_key_path

        raise ClickException(
            f"SSH {key_exists} key is missing at: {key_path}."
            " Make sure the keypair is passed!"
            " You can pass SSH keys in the EWC login command through the following flags `--ssh-private-key-path` or `--ssh-public-key-path`"
            "or let the `ewc login` command create them for you. Exiting."
        )


def init_command(
    application_credential_id: str,
    application_credential_secret: str,
    ssh_public_key_path: str,
    ssh_private_key_path: str,
    tenant_name: str,
    federee: str,
    profile: str = None,
    # token: str,
):
    """EWC CLI Login."""
    if not federee:
        # If --federee is not passed, ask interactively
        federee = select_provider()
        if not federee:
            console.print("No selection made. Exiting.")
            return

    console.print(f"Considering federee: {federee}")

    resolved_profile = _resolve_profile(profile, federee, tenant_name)

    ssh_private_key_path_to_save, ssh_public_key_path_to_save = check_and_generate_ssh_keys(
        ssh_public_key_path=ssh_public_key_path,
        ssh_private_key_path=ssh_private_key_path,
        resolved_profile=resolved_profile,
    )

    if openstack_config_available():
        console.print(
            "🔑 [bold green]Openstack cloud.yaml found at ~/.config/openstack/clouds.yaml[/bold green]"
            " – skipping Openstack ID and secret requirements."
        )
        application_credential_id = ""
        application_credential_secret = ""

    elif not application_credential_id or not application_credential_secret:
        if not application_credential_id:
            # Handle OpenStack credential ID
            application_credential_id = (
                application_credential_id
                or os.getenv("OS_APPLICATION_CREDENTIAL_ID")
                or click.prompt(
                    "Enter OpenStack Application Credential ID", hide_input=True
                )
            )

        if not application_credential_secret:
            # Handle OpenStack credential secret
            application_credential_secret = (
                application_credential_secret
                or os.getenv("OS_APPLICATION_CREDENTIAL_SECRET")
                or click.prompt(
                    "Enter OpenStack Application Credential Secret", hide_input=True
                )
            )

    # if kubeconfig_available():
    #     click.echo("🔑 kubeconfig found – skipping token requirement.")
    #     token = None
    # elif not token:
    #     token = click.prompt(
    #         "Enter Kubernetes token (leave blank if not needed)",
    #         hide_input=True,
    #         default="",
    #         show_default=False,
    #         prompt_suffix=": ",
    #     )
    #     if token == "":
    #         token = None

    #
    save_default_login_profile(
        federee=federee,
        tenant_name=tenant_name,
        ssh_private_key_path_to_save=ssh_private_key_path_to_save,
        ssh_public_key_path_to_save=ssh_public_key_path_to_save,
        # token=token,
        application_credential_id=application_credential_id,
        application_credential_secret=application_credential_secret,
    )

    # Save config
    save_cli_profile(
        federee=federee,
        tenant_name=tenant_name,
        ssh_private_key_path_to_save=ssh_private_key_path_to_save,
        ssh_public_key_path_to_save=ssh_public_key_path_to_save,
        profile=profile,
        # token=token,
        application_credential_id=application_credential_id,
        application_credential_secret=application_credential_secret,
    )

    console.print(
        f"✅ Profile '[bold cyan]{resolved_profile}[/bold cyan]' saved "
        f"in the following file {ewc_hub_config.EWC_CLI_PROFILES_PATH}"
    )
