"""
Provides a helper to access the legacy keyring which was supported up to version 1.6.1 of chik-blockchain. To use this
helper it's required to install the `legacy_keyring` extra dependency which can be done via the install-option `-l`.
"""

from __future__ import annotations

import sys
from typing import Callable, List, Union, cast

import click
from blspy import G1Element
from keyring.backends.macOS import Keyring as MacKeyring
from keyring.backends.Windows import WinVaultKeyring as WinKeyring

try:
    from keyrings.cryptfile.cryptfile import CryptFileKeyring
except ImportError:
    if sys.platform == "linux":
        sys.exit("Use `install.sh -l` to install the legacy_keyring dependency.")
    CryptFileKeyring = None


from chik.util.errors import KeychainUserNotFound
from chik.util.keychain import KeyData, KeyDataSecrets, get_private_key_user
from chik.util.misc import prompt_yes_no

LegacyKeyring = Union[MacKeyring, WinKeyring, CryptFileKeyring]


CURRENT_KEY_VERSION = "1.8"
DEFAULT_USER = f"user-chik-{CURRENT_KEY_VERSION}"  # e.g. user-chik-1.8
DEFAULT_SERVICE = f"chik-{DEFAULT_USER}"  # e.g. chik-user-chik-1.8
MAX_KEYS = 100


# casting to compensate for a combination of mypy and keyring issues
# https://github.com/python/mypy/issues/9025
# https://github.com/jaraco/keyring/issues/437
def create_legacy_keyring() -> LegacyKeyring:
    if sys.platform == "darwin":
        return cast(Callable[[], LegacyKeyring], MacKeyring)()
    elif sys.platform == "win32" or sys.platform == "cygwin":
        return cast(Callable[[], LegacyKeyring], WinKeyring)()
    elif sys.platform == "linux":
        keyring: CryptFileKeyring = CryptFileKeyring()
        keyring.keyring_key = "your keyring password"
        return keyring
    raise click.ClickException(f"platform '{sys.platform}' not supported.")


def generate_and_add(keyring: LegacyKeyring) -> KeyData:
    key = KeyData.generate()
    index = 0
    while True:
        try:
            get_key_data(keyring, index)
            index += 1
        except KeychainUserNotFound:
            keyring.set_password(
                DEFAULT_SERVICE,
                get_private_key_user(DEFAULT_USER, index),
                bytes(key.public_key).hex() + key.entropy.hex(),
            )
            return key


def get_key_data(keyring: LegacyKeyring, index: int) -> KeyData:
    user = get_private_key_user(DEFAULT_USER, index)
    read_str = keyring.get_password(DEFAULT_SERVICE, user)
    if read_str is None or len(read_str) == 0:
        raise KeychainUserNotFound(DEFAULT_SERVICE, user)
    str_bytes = bytes.fromhex(read_str)

    public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE])
    fingerprint = public_key.get_fingerprint()
    entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32]

    return KeyData(
        fingerprint=fingerprint,
        public_key=public_key,
        label=None,
        secrets=KeyDataSecrets.from_entropy(entropy),
    )


def get_keys(keyring: LegacyKeyring) -> List[KeyData]:
    keys: List[KeyData] = []
    for index in range(MAX_KEYS + 1):
        try:
            keys.append(get_key_data(keyring, index))
        except KeychainUserNotFound:
            pass
    return keys


def print_key(key: KeyData) -> None:
    print(f"fingerprint: {key.fingerprint}, mnemonic: {key.mnemonic_str()}")


def print_keys(keyring: LegacyKeyring) -> None:
    keys = get_keys(keyring)

    if len(keys) == 0:
        raise click.ClickException("No keys found in the legacy keyring.")

    for key in keys:
        print_key(key)


def remove_keys(keyring: LegacyKeyring) -> None:
    removed = 0
    for index in range(MAX_KEYS + 1):
        try:
            keyring.delete_password(DEFAULT_SERVICE, get_private_key_user(DEFAULT_USER, index))
            removed += 1
        except Exception:
            pass

    print(f"{removed} key{'s' if removed != 1 else ''} removed.")


@click.group(help="Manage the keys in the legacy keyring.")
def legacy_keyring() -> None:
    pass


@legacy_keyring.command(help="Generate and add a random key (for testing)", hidden=True)
def generate() -> None:
    keyring = create_legacy_keyring()
    key = generate_and_add(keyring)
    print_key(key)


@legacy_keyring.command(help="Show all available keys")
def show() -> None:
    print_keys(create_legacy_keyring())


@legacy_keyring.command(help="Remove all keys")
def clear() -> None:
    keyring = create_legacy_keyring()

    print_keys(keyring)

    if not prompt_yes_no("\nDo you really want to remove all the keys from the legacy keyring? This can't be undone."):
        raise click.ClickException("Aborted!")

    remove_keys(keyring)


if __name__ == "__main__":
    legacy_keyring()
