import asyncio
import threading
import time

import click
from click import Context
from requests.exceptions import ConnectionError

from cashu.core.settings import NOSTR_PRIVATE_KEY, NOSTR_RELAYS
from cashu.nostr.nostr.client.client import NostrClient
from cashu.nostr.nostr.event import Event
from cashu.nostr.nostr.key import PublicKey
from cashu.wallet.cli.cli_helpers import get_mint_wallet
from cashu.wallet.crud import (
    get_nostr_last_check_timestamp,
    set_nostr_last_check_timestamp,
)
from cashu.wallet.wallet import Wallet


async def nip5_to_pubkey(wallet: Wallet, address: str):
    """
    Retrieves the nostr public key of a NIP-05 identifier.
    """
    # we will be using the requests session from the wallet
    await wallet._init_s()
    # if no username is given, use default _ (NIP-05 stuff)
    if "@" not in address:
        address = "_@" + address
    # now we can use it
    user, host = address.split("@")
    resp_dict = {}
    try:
        resp = wallet.s.get(
            f"https://{host}/.well-known/nostr.json?name={user}",
        )
        resp.raise_for_status()
    except ConnectionError:
        raise Exception(f"Could not connect to {host}")
    except Exception as e:
        raise e
    resp_dict = resp.json()
    assert "names" in resp_dict, Exception(f"did not receive any names from {host}")
    assert user in resp_dict["names"], Exception(f"{user}@{host} not found")
    pubkey = resp_dict["names"][user]
    return pubkey


async def send_nostr(ctx: Context, amount: int, pubkey: str, verbose: bool, yes: bool):
    """
    Sends tokens via nostr.
    """
    # load a wallet for the chosen mint
    wallet = await get_mint_wallet(ctx)

    if "@" in pubkey or "." in pubkey:
        # matches user@domain.com and domain.com (which is _@domain.com)
        pubkey = await nip5_to_pubkey(wallet, pubkey)

    await wallet.load_proofs()
    _, send_proofs = await wallet.split_to_send(
        wallet.proofs, amount, set_reserved=True
    )
    token = await wallet.serialize_proofs(send_proofs)

    if pubkey.startswith("npub"):
        pubkey_to = PublicKey().from_npub(pubkey)
    else:
        pubkey_to = PublicKey(bytes.fromhex(pubkey))

    print("")
    print(token)

    if not yes:
        print("")
        click.confirm(
            f"Send {amount} sat to {pubkey_to.bech32()}?",
            abort=True,
            default=True,
        )

    client = NostrClient(private_key=NOSTR_PRIVATE_KEY or "", relays=NOSTR_RELAYS)
    if verbose and not NOSTR_PRIVATE_KEY:
        # we generated a random key if none was present
        print(f"Your nostr private key: {client.private_key.bech32()}")

    client.dm(token, pubkey_to)
    print(f"Token sent to {pubkey_to.bech32()}")
    await asyncio.sleep(5)
    client.close()


async def receive_nostr(ctx: Context, verbose: bool):
    if NOSTR_PRIVATE_KEY is None:
        print(
            "Warning: No nostr private key set! You don't have NOSTR_PRIVATE_KEY set in your .env file. I will create a random private key for this session but I will not remember it."
        )
        print("")
    client = NostrClient(private_key=NOSTR_PRIVATE_KEY, relays=NOSTR_RELAYS)
    print(f"Your nostr public key: {client.public_key.bech32()}")
    if verbose:
        print(f"Your nostr private key (do not share!): {client.private_key.bech32()}")
    await asyncio.sleep(2)

    def get_token_callback(event: Event, decrypted_content):
        if verbose:
            print(
                f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}"
            )
        try:
            # call the receive method
            from cashu.wallet.cli.cli import receive

            asyncio.run(receive(ctx, decrypted_content, ""))
        except Exception as e:
            pass

    # determine timestamp of last check so we don't scan all historical DMs
    wallet: Wallet = ctx.obj["WALLET"]
    last_check = await get_nostr_last_check_timestamp(db=wallet.db)
    if last_check:
        last_check -= 60 * 60  # 1 hour tolerance
    await set_nostr_last_check_timestamp(timestamp=int(time.time()), db=wallet.db)

    t = threading.Thread(
        target=client.get_dm,
        args=(client.public_key, get_token_callback, {"since": last_check}),
        name="Nostr DM",
    )
    t.start()
