"""
Provides a helper to access the legacy keyring which was supported up to version 1.6.1 of chia-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 chia_rs import G1Element
from keyring.backends.macOS import Keyring as MacKeyring
from keyring.backends.Windows import WinVaultKeyring as WinKeyring

from chia.util.ints import uint32

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 chia.cmds.cmds_util import prompt_yes_no
from chia.util.errors import KeychainUserNotFound
from chia.util.keychain import KeyData, KeyDataSecrets, get_private_key_user

LegacyKeyring = Union[MacKeyring, WinKeyring, CryptFileKeyring]


CURRENT_KEY_VERSION = "1.8"
DEFAULT_USER = f"user-chia-{CURRENT_KEY_VERSION}"  # e.g. user-chia-1.8
DEFAULT_SERVICE = f"chia-{DEFAULT_USER}"  # e.g. chia-user-chia-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=uint32(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()
