"""
fluxwallet CLI — command-line interface for the Flux wallet library.

Usage:
    fluxwallet wallet list
    fluxwallet wallet info bernard_ssp
    fluxwallet tx send bernard_ssp t1abc... 10000000
    fluxwallet ssp pair bernard_ssp
    fluxwallet mnemonic
"""

from __future__ import annotations

import asyncio
import io
from functools import wraps
from typing import Annotated, Optional

import qrcode
import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table

from fluxwallet.db import Db
from fluxwallet.wallet import Wallet, WalletError

console = Console()

# ---------------------------------------------------------------------------
# Async bridge
# ---------------------------------------------------------------------------


def async_command(f):
    """Wrap an async function for use as a typer command."""

    @wraps(f)
    def wrapper(*args, **kwargs):
        async def _run():
            await Db.start()
            try:
                return await f(*args, **kwargs)
            finally:
                await Db.close_all()

        return asyncio.run(_run())

    return wrapper


def _handle_error(e: Exception) -> None:
    console.print(f"[bold red]Error:[/bold red] {e}")
    raise typer.Exit(1)


def _print_qr(data: str) -> None:
    qr = qrcode.QRCode(
        version=None,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=1,
        border=1,
    )
    qr.add_data(data)
    qr.make(fit=True)
    buf = io.StringIO()
    qr.print_ascii(out=buf, invert=True)
    console.print(buf.getvalue())


# ---------------------------------------------------------------------------
# App structure
# ---------------------------------------------------------------------------

app = typer.Typer(name="fluxwallet", help="Flux blockchain wallet CLI", no_args_is_help=True)
wallet_app = typer.Typer(help="Wallet management", no_args_is_help=True)
key_app = typer.Typer(help="Key and address management", no_args_is_help=True)
tx_app = typer.Typer(help="Transactions", no_args_is_help=True)
ssp_app = typer.Typer(help="SSP 2-of-2 multisig co-signing", no_args_is_help=True)
db_app = typer.Typer(help="Database operations", no_args_is_help=True)

app.add_typer(wallet_app, name="wallet")
app.add_typer(key_app, name="key")
app.add_typer(tx_app, name="tx")
app.add_typer(ssp_app, name="ssp")
app.add_typer(db_app, name="db")


# ---------------------------------------------------------------------------
# wallet commands
# ---------------------------------------------------------------------------


@wallet_app.command("list")
@async_command
async def wallet_list():
    """List all wallets."""
    from fluxwallet.wallets import wallets_list

    wallets = await wallets_list()
    if not wallets:
        console.print("No wallets found.")
        return

    PURPOSE_LABELS = {44: "standard", 48: "multisig"}

    table = Table(title="Wallets")
    table.add_column("ID", style="dim")
    table.add_column("Name", style="bold")
    table.add_column("Network")
    table.add_column("Type")

    for w in wallets:
        purpose = w["purpose"]
        table.add_row(
            str(w["id"]),
            w["name"],
            w["network"],
            PURPOSE_LABELS.get(purpose, f"BIP{purpose}"),
        )

    console.print(table)


@wallet_app.command("create")
@async_command
async def wallet_create(
    name: Annotated[str, typer.Argument(help="Wallet name")],
    mnemonic: Annotated[Optional[str], typer.Option("--mnemonic", "-m", help="Mnemonic seed phrase")] = None,
    network: Annotated[str, typer.Option("--network", "-n", help="Network name")] = "flux",
):
    """Create a new wallet."""
    try:
        keys = mnemonic or ""
        w = await Wallet.create(name, keys=keys, network=network)
        key = await w.new_key()

        console.print(Panel(
            f"[bold]Name:[/bold]    {w.name}\n"
            f"[bold]Network:[/bold] {w.network.name}\n"
            f"[bold]Address:[/bold] {key.address}",
            title="Wallet Created",
        ))
    except WalletError as e:
        _handle_error(e)


@wallet_app.command("info")
@async_command
async def wallet_info(
    name: Annotated[str, typer.Argument(help="Wallet name")],
):
    """Show wallet details, balance, and receive address."""
    try:
        w = await Wallet.open(name)
        with console.status("Updating from network..."):
            await w.utxos_update()
        balance = await w.balance(as_string=True)
        key = await w.get_key()

        info = (
            f"[bold]Name:[/bold]      {w.name}\n"
            f"[bold]Network:[/bold]   {w.network.name}\n"
            f"[bold]Scheme:[/bold]    {w.scheme}\n"
            f"[bold]Multisig:[/bold]  {w.multisig}\n"
            f"[bold]Balance:[/bold]   {balance}\n"
            f"[bold]Receive:[/bold]   {key.address}"
        )
        if w.ssp_peer_pubkey:
            info += f"\n[bold]SSP:[/bold]       paired"

        console.print(Panel(info, title=w.name))
    except WalletError as e:
        _handle_error(e)


@wallet_app.command("rename")
@async_command
async def wallet_rename(
    name: Annotated[str, typer.Argument(help="Current wallet name")],
    new_name: Annotated[str, typer.Argument(help="New wallet name")],
):
    """Rename a wallet."""
    try:
        w = await Wallet.open(name)
        await w.set_name(new_name)
        console.print(f"Renamed '{name}' to '{new_name}'.")
    except WalletError as e:
        _handle_error(e)


@wallet_app.command("delete")
@async_command
async def wallet_delete(
    name: Annotated[str, typer.Argument(help="Wallet name")],
    force: Annotated[bool, typer.Option("--force", "-f", help="Skip confirmation")] = False,
):
    """Delete a wallet."""
    from fluxwallet.wallets import wallet_delete as _wallet_delete

    if not force:
        typer.confirm(f"Delete wallet '{name}'?", abort=True)
    try:
        await _wallet_delete(name, force=True)
        console.print(f"Wallet '{name}' deleted.")
    except WalletError as e:
        _handle_error(e)


@wallet_app.command("balance")
@async_command
async def wallet_balance(
    name: Annotated[str, typer.Argument(help="Wallet name")],
):
    """Show wallet balance."""
    try:
        w = await Wallet.open(name)
        with console.status("Updating from network..."):
            await w.utxos_update()
        balance = await w.balance(as_string=True)
        console.print(f"{balance}")
    except WalletError as e:
        _handle_error(e)


@wallet_app.command("scan")
@async_command
async def wallet_scan(
    name: Annotated[str, typer.Argument(help="Wallet name")],
):
    """Full scan wallet from network."""
    try:
        w = await Wallet.open(name)
        with console.status("Scanning..."):
            await w.scan()
        balance = await w.balance(as_string=True)
        console.print(f"Scan complete. Balance: {balance}")
    except WalletError as e:
        _handle_error(e)


# ---------------------------------------------------------------------------
# key commands
# ---------------------------------------------------------------------------


@key_app.command("import")
@async_command
async def key_import(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
    key: Annotated[str, typer.Argument(help="Private key (WIF) or public key")],
):
    """Import a key into a wallet."""
    try:
        w = await Wallet.open(wallet)
        wk = await w.import_key(key)
        console.print(f"Imported: {wk.address}")
    except WalletError as e:
        _handle_error(e)


@key_app.command("address")
@async_command
async def key_address(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
    qr: Annotated[bool, typer.Option("--qr", help="Display QR code")] = False,
):
    """Show next unused receive and change addresses."""
    try:
        w = await Wallet.open(wallet)
        receive = await w.get_key()
        change = await w.get_key_change()
        console.print(f"[bold]Receive:[/bold] {receive.address}")
        console.print(f"[bold]Change:[/bold]  {change.address}")
        if qr:
            _print_qr(receive.address)
    except WalletError as e:
        _handle_error(e)


@key_app.command("list")
@async_command
async def key_list(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
):
    """List wallet keys and addresses."""
    from fluxwallet.wallet.wallet import AUTH_ONLY_NETWORKS

    try:
        w = await Wallet.open(wallet)
        keys = await w.keys()

        table = Table(title=f"Keys — {wallet}")
        table.add_column("Path", style="dim")
        table.add_column("Address")
        table.add_column("Used")
        table.add_column("Type")

        for k in keys:
            if k.network_name in AUTH_ONLY_NETWORKS:
                continue
            key_type = "change" if k.change else "receive"
            if k.depth and k.depth < 4:
                key_type = "master" if k.depth == 0 else "account"
            table.add_row(
                str(k.path) if k.path else "",
                str(k.address) if k.address else "",
                "yes" if k.used else "",
                key_type,
            )

        console.print(table)
    except WalletError as e:
        _handle_error(e)


@key_app.command("new")
@async_command
async def key_new(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
    change: Annotated[bool, typer.Option("--change", help="Generate change address")] = False,
    qr: Annotated[bool, typer.Option("--qr", help="Display QR code")] = False,
):
    """Generate a new receiving address."""
    try:
        w = await Wallet.open(wallet)
        key = await w.new_key(change=1 if change else 0)
        console.print(key.address)
        if qr:
            _print_qr(key.address)
    except WalletError as e:
        _handle_error(e)


# ---------------------------------------------------------------------------
# tx commands
# ---------------------------------------------------------------------------


@tx_app.command("list")
@async_command
async def tx_list(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
):
    """List wallet transactions."""
    try:
        w = await Wallet.open(wallet)
        txs = await w.transactions(as_dict=True)

        if not txs:
            console.print("No transactions.")
            return

        table = Table(title=f"Transactions — {wallet}")
        table.add_column("TxID", style="dim")
        table.add_column("Date")
        table.add_column("Confirmations", justify="right")
        table.add_column("Status")

        for tx in txs:
            txid = str(tx.get("txid", ""))
            table.add_row(
                txid[:16] + "..." if len(txid) > 16 else txid,
                str(tx.get("date", "")),
                str(tx.get("confirmations", "")),
                str(tx.get("status", "")),
            )

        console.print(table)
    except WalletError as e:
        _handle_error(e)


@tx_app.command("utxos")
@async_command
async def tx_utxos(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
):
    """List unspent transaction outputs."""
    try:
        w = await Wallet.open(wallet)
        with console.status("Updating UTXOs..."):
            await w.utxos_update()
        utxos = await w.utxos()

        if not utxos:
            console.print("No UTXOs.")
            return

        table = Table(title=f"UTXOs — {wallet}")
        table.add_column("TxID", style="dim")
        table.add_column("Index", justify="right")
        table.add_column("Address")
        table.add_column("Value", justify="right")
        table.add_column("Confirmations", justify="right")

        from fluxwallet.values import Value

        for u in utxos:
            txid = str(u.get("txid", ""))
            value = Value.from_satoshi(u["value"], network="flux")
            table.add_row(
                txid[:16] + "..." if len(txid) > 16 else txid,
                str(u.get("output_n", "")),
                str(u.get("address", "")),
                str(value),
                str(u.get("confirmations", "")),
            )

        console.print(table)
    except WalletError as e:
        _handle_error(e)


@tx_app.command("update")
@async_command
async def tx_update(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
):
    """Update UTXOs from network."""
    try:
        w = await Wallet.open(wallet)
        with console.status("Updating UTXOs..."):
            await w.utxos_update()
        balance = await w.balance(as_string=True)
        console.print(f"Updated. Balance: {balance}")
    except WalletError as e:
        _handle_error(e)


@tx_app.command("send")
@async_command
async def tx_send(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
    address: Annotated[str, typer.Argument(help="Destination address")],
    amount: Annotated[str, typer.Argument(help="Amount in FLUX (e.g. 1.5) or satoshis with --sat")],
    sat: Annotated[bool, typer.Option("--sat", help="Amount is in satoshis")] = False,
    fee: Annotated[Optional[int], typer.Option("--fee", help="Fee in satoshis")] = None,
    yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
    relay: Annotated[str, typer.Option("--relay", help="SSP relay server URL")] = "relay.sspwallet.io",
    timeout: Annotated[int, typer.Option("--timeout", help="SSP Key approval timeout in seconds")] = 300,
):
    """Send a transaction. Automatically uses SSP co-signing for multisig wallets."""
    from fluxwallet.values import Value, value_to_satoshi

    ssp = None
    try:
        if sat:
            amount_sat = int(amount)
        else:
            amount_sat = value_to_satoshi(amount)

        w = await Wallet.open(wallet)
        with console.status("Updating UTXOs..."):
            await w.utxos_update()

        amt_str = Value.from_satoshi(amount_sat, network="flux").str_unit()

        if not yes:
            typer.confirm(f"Send {amt_str} to {address}?", abort=True)

        if w.multisig and w.ssp_peer_pubkey:
            # SSP multisig — co-sign via relay
            from fluxwallet.ssp import SSPWallet
            ssp = await SSPWallet.from_existing(w, relay)
            with console.status("Waiting for SSP Key approval..."):
                txid = await ssp.send([(address, amount_sat)], fee=fee, timeout=timeout)
            console.print(Panel(
                f"[bold]TxID:[/bold] {txid}",
                title="Transaction Sent",
            ))
        else:
            # Standard single-sig send
            wt = await w.send([(address, amount_sat)], fee=fee, offline=False)
            console.print(Panel(
                f"[bold]TxID:[/bold] {wt.tx.txid}",
                title="Transaction Sent",
            ))
    except Exception as e:
        _handle_error(e)
    finally:
        if ssp:
            await ssp.close()


@tx_app.command("sweep")
@async_command
async def tx_sweep(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
    address: Annotated[str, typer.Argument(help="Destination address")],
    fee: Annotated[Optional[int], typer.Option("--fee", help="Fee in satoshis")] = None,
    yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation")] = False,
):
    """Sweep all funds to an address."""
    try:
        w = await Wallet.open(wallet)
        with console.status("Updating UTXOs..."):
            await w.utxos_update()
        balance = await w.balance(as_string=True)

        if not yes:
            typer.confirm(f"Sweep {balance} to {address}?", abort=True)

        wt = await w.sweep(address, fee=fee, offline=False)
        console.print(Panel(
            f"[bold]TxID:[/bold] {wt.tx.txid}",
            title="Sweep Sent",
        ))
    except (WalletError, Exception) as e:
        _handle_error(e)


# ---------------------------------------------------------------------------
# ssp commands
# ---------------------------------------------------------------------------


@ssp_app.command("pair")
@async_command
async def ssp_pair(
    wallet: Annotated[Optional[str], typer.Argument(help="Existing multisig wallet to re-pair")] = None,
    new: Annotated[Optional[str], typer.Option("--new", help="Create new multisig wallet with this name")] = None,
    mnemonic: Annotated[Optional[str], typer.Option("--mnemonic", "-m", help="Mnemonic seed phrase (for --new)")] = None,
    relay: Annotated[str, typer.Option("--relay", help="Relay server URL")] = "relay.sspwallet.io",
):
    """Pair or re-pair a wallet with SSP Key."""
    from fluxwallet.mnemonic import Mnemonic
    from fluxwallet.ssp import SSPWallet
    from fluxwallet.wallets import wallet_exists

    if not wallet and not new:
        console.print("[bold red]Error:[/bold red] Provide a wallet name to re-pair, or --new NAME to create one.")
        raise typer.Exit(1)

    ssp = None
    try:
        if new:
            if await wallet_exists(new):
                _handle_error(WalletError(f"Wallet '{new}' already exists"))

            if mnemonic:
                seed_phrase = mnemonic
            else:
                m = Mnemonic()
                seed_phrase = m.generate()
                console.print(Panel(
                    seed_phrase,
                    title="Generated Mnemonic — SAVE THIS",
                    border_style="red",
                ))

            ssp = await SSPWallet.create_paired(new, seed_phrase, relay)
            is_repair = False
            name = new
        else:
            w = await Wallet.open(wallet)
            if not w.multisig:
                _handle_error(WalletError(f"'{wallet}' is not a multisig wallet. Use --new to create one."))
            ssp = await SSPWallet.repair(w, relay)
            is_repair = True
            name = wallet

        # Step 1: BTC identity sync
        btc_xpub = ssp.xpub_identity()
        identity = ssp.identity_key()

        console.print(Panel(f"Scan with SSP Key (BTC identity)", title="Step 1"))
        _print_qr(btc_xpub)
        console.print(f"Manual: btc:{btc_xpub}", soft_wrap=True)

        with console.status("Waiting for SSP Key BTC sync..."):
            await ssp.wait_for_btc_sync(timeout=300)
        console.print("[green]BTC sync received.[/green]")

        # Step 2: Flux chain sync
        flux_xpub = await ssp.pair_with_key()

        console.print(Panel(f"Switch to Flux in SSP Key, then scan", title="Step 2"))
        _print_qr(flux_xpub)
        console.print(f"Manual: flux:{flux_xpub}", soft_wrap=True)
        console.print(f"Identity: {identity.address()}")

        with console.status("Waiting for SSP Key Flux sync..."):
            await ssp.wait_for_chain_sync(timeout=300)
        console.print("[green]Flux sync received.[/green]")

        # Step 3: Complete
        if is_repair:
            await ssp.complete_repair()
            console.print(Panel(f"Re-pairing complete for: {name}", title="Done"))
        else:
            ms_wallet = await ssp.complete_pairing()
            key = await ms_wallet.new_key()
            console.print(Panel(
                f"[bold]Wallet:[/bold]  {ms_wallet.name}\n"
                f"[bold]Address:[/bold] {key.address}",
                title="Multisig Wallet Created",
            ))

    except KeyboardInterrupt:
        console.print("\nPairing cancelled.")
        raise typer.Exit(1)
    except Exception as e:
        _handle_error(e)
    finally:
        if ssp:
            await ssp.close()


@ssp_app.command("info")
@async_command
async def ssp_info(
    wallet: Annotated[str, typer.Argument(help="Wallet name")],
):
    """Show SSP wallet identity info."""
    try:
        w = await Wallet.open(wallet)

        info = (
            f"[bold]Name:[/bold]      {w.name}\n"
            f"[bold]Multisig:[/bold]  {w.multisig}\n"
            f"[bold]Network:[/bold]   {w.network.name}"
        )

        if w.ssp_peer_pubkey:
            from fluxwallet.ssp import _derive_relay_auth

            private_cosigner = None
            for cs in w.cosigner:
                if cs.main_key and cs.main_key.is_private:
                    private_cosigner = cs
                    break

            if private_cosigner:
                master_key = private_cosigner.main_key.key()
                wk_identity, witness_script, our_pubkey = _derive_relay_auth(
                    master_key, w.ssp_peer_pubkey,
                )
                info += (
                    f"\n[bold]Paired:[/bold]    yes"
                    f"\n[bold]wkIdentity:[/bold]{wk_identity}"
                    f"\n[bold]Our pubkey:[/bold]{our_pubkey[:16]}..."
                    f"\n[bold]Peer pubkey:[/bold]{w.ssp_peer_pubkey[:16]}..."
                )
        else:
            info += f"\n[bold]Paired:[/bold]    no"

        console.print(Panel(info, title=f"SSP — {w.name}"))
    except WalletError as e:
        _handle_error(e)


# ---------------------------------------------------------------------------
# db commands
# ---------------------------------------------------------------------------


@db_app.command("info")
@async_command
async def db_info():
    """Show database info."""
    from fluxwallet.config.config import DEFAULT_DATABASE
    console.print(Panel(
        f"[bold]Path:[/bold] {DEFAULT_DATABASE}",
        title="Database",
    ))


@db_app.command("encrypt")
@async_command
async def db_encrypt():
    """Encrypt the database with a password."""
    password = typer.prompt("Password", hide_input=True)
    confirm = typer.prompt("Confirm password", hide_input=True)
    if password != confirm:
        _handle_error(Exception("Passwords do not match"))

    db = Db()
    result = await db.encrypt_with_password(password)
    console.print(f"Database encrypted. Encryption key: {result}")


@db_app.command("unlock")
@async_command
async def db_unlock():
    """Unlock an encrypted database."""
    password = typer.prompt("Password", hide_input=True)
    db = Db()
    success = await db.unlock_with_password(password)
    if success:
        console.print("[green]Database unlocked.[/green]")
    else:
        _handle_error(Exception("Failed to unlock database. Wrong password?"))


# ---------------------------------------------------------------------------
# top-level commands
# ---------------------------------------------------------------------------


@app.command("mnemonic")
def mnemonic_generate(
    words: Annotated[int, typer.Option("--words", "-w", help="Number of words (12 or 24)")] = 24,
):
    """Generate a new mnemonic seed phrase."""
    from fluxwallet.mnemonic import Mnemonic

    if words not in (12, 24):
        _handle_error(Exception("--words must be 12 or 24"))

    strength = 256 if words == 24 else 128
    m = Mnemonic()
    phrase = m.generate(strength=strength)

    console.print(Panel(
        phrase,
        title=f"Mnemonic ({words} words)",
        subtitle="SAVE THIS SECURELY",
        border_style="yellow",
    ))
