"""corpo CLI — agent-facing DAO governance tool for Solana spl-governance."""

import sys

import click

from corpo import __version__
from corpo.client import CorpoAPIError, CorpoClient
from corpo.auth_config import (
    get_api_key,
    get_api_url,
    load_auth_config,
    require_api_key,
    save_auth_config,
    VALID_KEYS,
)
from corpo.config import DEFAULTS, load_config, resolve
from corpo.output import error, success
from corpo.pda import find_token_owner_record
from corpo.signer import generate_keypair, load_keypair, save_keypair, sign_and_send

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _resolve_opts(ctx: click.Context) -> dict:
    """Build a resolved config dict from CLI options, env, config file."""
    cfg = ctx.obj.get("config_data", {})
    return {
        "keypair": resolve(
            ctx.obj.get("keypair"), "CORPO_KEYPAIR", cfg, "keypair", DEFAULTS["keypair"]
        ),
        "api_url": resolve(
            ctx.obj.get("api_url"), "CORPO_API_URL", cfg, "api_url", DEFAULTS["api_url"]
        ),
        "rpc_url": resolve(
            ctx.obj.get("rpc_url"), "CORPO_RPC_URL", cfg, "rpc_url", DEFAULTS["rpc_url"]
        ),
        "realm": resolve(
            ctx.obj.get("realm"), "CORPO_REALM", cfg, "realm", DEFAULTS["realm"]
        ),
        "governance": resolve(
            ctx.obj.get("governance"),
            "CORPO_GOVERNANCE",
            cfg,
            "governance",
            DEFAULTS["governance"],
        ),
        "governing_token_mint": resolve(
            ctx.obj.get("governing_token_mint"),
            "CORPO_GOVERNING_TOKEN_MINT",
            cfg,
            "governing_token_mint",
            DEFAULTS["governing_token_mint"],
        ),
        "program_id": resolve(
            ctx.obj.get("program_id"),
            "CORPO_PROGRAM_ID",
            cfg,
            "program_id",
            DEFAULTS["program_id"],
        ),
    }


def _get_client(opts: dict) -> CorpoClient:
    return CorpoClient(opts["api_url"])


def _require(opts: dict, key: str, flag_name: str) -> str:
    val = opts.get(key, "")
    if not val:
        error(f"{flag_name} is required", hint=f"Set --{flag_name}, env CORPO_{key.upper()}, or config.toml [{key}]")
    return val


def _sign_send(opts: dict, tx_resp: dict) -> dict:
    """Load keypair, sign the transaction, send to RPC."""
    kp_path = opts["keypair"]
    try:
        kp = load_keypair(kp_path)
    except Exception as e:
        error(f"Failed to load keypair from {kp_path}: {e}", hint="Run 'corpo init' to generate a keypair")

    tx_b64 = tx_resp["transaction"]
    rpc_url = opts["rpc_url"]
    try:
        sig = sign_and_send(tx_b64, kp, rpc_url)
    except Exception as e:
        error(f"Transaction failed: {e}")

    return {"signature": sig}


def _api_call(fn, *args, **kwargs):
    """Call a CorpoClient method with error handling."""
    try:
        return fn(*args, **kwargs)
    except CorpoAPIError as e:
        error(e.message)
    except Exception as e:
        error(str(e))


# ---------------------------------------------------------------------------
# CLI group
# ---------------------------------------------------------------------------

HELP_TEXT = """\
corpo — Agent-facing DAO governance CLI for Solana spl-governance.

Discover what you can do:
  corpo --help              Show this help
  corpo <command> --help    Show help for a specific command

URGENT: KEYPAIR SECURITY
  Your keypair at ~/.corpo/keypair.json controls real funds on Solana.
  1. BACK UP your keypair immediately: cp ~/.corpo/keypair.json <backup location>
  2. NEVER share your keypair file or its contents
  3. If you lose your keypair, you lose access to all DAOs and funds

HUMAN CONTACT
  If you need human assistance or encounter situations requiring judgment:
  Install clawbuddy: uvx clawbuddy
  Message another agent: uvx clawbuddy msg <agent-id>
"""


@click.group(help=HELP_TEXT)
@click.version_option(__version__, prog_name="corpo")
@click.option("--config", "config_path", default=None, help="Config file path [default: ~/.corpo/config.toml]")
@click.option("--keypair", default=None, envvar="CORPO_KEYPAIR", help="Keypair file path [env: CORPO_KEYPAIR]")
@click.option("--api-url", default=None, envvar="CORPO_API_URL", help="API server URL [env: CORPO_API_URL]")
@click.option("--rpc-url", default=None, envvar="CORPO_RPC_URL", help="Solana RPC URL [env: CORPO_RPC_URL]")
@click.option("--realm", default=None, envvar="CORPO_REALM", help="Default realm pubkey [env: CORPO_REALM]")
@click.option("--governance", default=None, envvar="CORPO_GOVERNANCE", help="Default governance pubkey [env: CORPO_GOVERNANCE]")
@click.option("--governing-token-mint", default=None, envvar="CORPO_GOVERNING_TOKEN_MINT", help="Default governing token mint [env: CORPO_GOVERNING_TOKEN_MINT]")
@click.option("--program-id", default=None, envvar="CORPO_PROGRAM_ID", help="spl-governance program ID [env: CORPO_PROGRAM_ID]")
@click.pass_context
def cli(ctx, config_path, keypair, api_url, rpc_url, realm, governance, governing_token_mint, program_id):
    ctx.ensure_object(dict)
    ctx.obj["config_data"] = load_config(config_path)
    ctx.obj["keypair"] = keypair
    ctx.obj["api_url"] = api_url
    ctx.obj["rpc_url"] = rpc_url
    ctx.obj["realm"] = realm
    ctx.obj["governance"] = governance
    ctx.obj["governing_token_mint"] = governing_token_mint
    ctx.obj["program_id"] = program_id


# ---------------------------------------------------------------------------
# init
# ---------------------------------------------------------------------------

@cli.command()
@click.pass_context
def init(ctx):
    """Generate keypair, create config.toml, print backup instructions.

    Output: {"ok": true, "data": {"pubkey": "...", "keypair_path": "...", "config_path": "..."}}
    """
    from pathlib import Path
    import tomllib

    corpo_dir = Path("~/.corpo").expanduser()
    kp_path = corpo_dir / "keypair.json"
    cfg_path = corpo_dir / "config.toml"

    if kp_path.exists():
        kp = load_keypair(kp_path)
        pubkey = str(kp.pubkey())
    else:
        kp = generate_keypair()
        save_keypair(kp, kp_path)
        pubkey = str(kp.pubkey())

    if not cfg_path.exists():
        cfg_path.write_text(
            f"""[identity]
keypair = "~/.corpo/keypair.json"

[network]
api_url = "https://api.corpo.dev"
rpc_url = "https://api.devnet.solana.com"
program_id = "GTesTBiEWE32WHXXE2S4XbZvA5CrEc4xs6ZgRe895dP"

[defaults]
realm = ""
governance = ""
governing_token_mint = ""
"""
        )

    success({
        "pubkey": pubkey,
        "keypair_path": str(kp_path),
        "config_path": str(cfg_path),
        "URGENT": "BACK UP your keypair immediately: cp ~/.corpo/keypair.json <safe location>",
    })


# ---------------------------------------------------------------------------
# Read commands
# ---------------------------------------------------------------------------

@cli.command()
@click.pass_context
def status(ctx):
    """Show identity and realm context.

    Output: {"ok": true, "data": {"pubkey": "...", "realm": {...}, "member": {...}}}
    """
    opts = _resolve_opts(ctx)
    kp_path = opts["keypair"]
    try:
        kp = load_keypair(kp_path)
        pubkey = str(kp.pubkey())
    except Exception:
        pubkey = None

    data: dict = {"pubkey": pubkey}
    client = _get_client(opts)

    realm_addr = opts.get("realm", "")
    if realm_addr:
        try:
            data["realm"] = client.get_realm(realm_addr)
        except Exception:
            data["realm"] = None

        mint = opts.get("governing_token_mint", "")
        if pubkey and mint:
            try:
                data["member"] = client.get_member(realm_addr, mint, pubkey)
            except Exception:
                data["member"] = None

    success(data)


@cli.command()
@click.option("--address", default=None, help="Realm address (pubkey)")
@click.option("--name", default=None, help="Realm name")
@click.pass_context
def realm(ctx, address, name):
    """Look up a realm by address or name.

    Output: {"ok": true, "data": {"address": "...", "realm": {...}}}
    """
    opts = _resolve_opts(ctx)
    client = _get_client(opts)

    if address:
        result = _api_call(client.get_realm, address)
    elif name:
        result = _api_call(client.get_realm_by_name, name)
    else:
        # Fall back to configured realm
        addr = _require(opts, "realm", "address or --name")
        result = _api_call(client.get_realm, addr)
    success(result)


@cli.command()
@click.pass_context
def realms(ctx):
    """List all realms.

    Output: {"ok": true, "data": [...]}
    """
    opts = _resolve_opts(ctx)
    client = _get_client(opts)
    result = _api_call(client.list_realms)
    success({"realms": result})


@cli.command()
@click.option("--governance", default=None, help="Governance address [from config]")
@click.pass_context
def proposals(ctx, governance):
    """List proposals for a governance.

    Output: {"ok": true, "data": {"proposals": [...]}}
    """
    opts = _resolve_opts(ctx)
    if governance:
        opts["governance"] = governance
    gov = _require(opts, "governance", "governance")
    client = _get_client(opts)
    result = _api_call(client.list_proposals, gov)
    success({"proposals": result})


@cli.command()
@click.option("--realm", "realm_addr", default=None, help="Realm address [from config]")
@click.pass_context
def members(ctx, realm_addr):
    """List members of a realm.

    Output: {"ok": true, "data": {"members": [...]}}
    """
    opts = _resolve_opts(ctx)
    if realm_addr:
        opts["realm"] = realm_addr
    r = _require(opts, "realm", "realm")
    client = _get_client(opts)
    result = _api_call(client.list_members, r)
    success({"members": result})


# ---------------------------------------------------------------------------
# Admin commands (signing required)
# ---------------------------------------------------------------------------

@cli.command("create-realm")
@click.option("--name", required=True, help="Realm name")
@click.option("--community-mint", required=True, help="Community token mint pubkey")
@click.option("--council-mint", default=None, help="Council token mint pubkey (optional)")
@click.option("--min-weight", default=1000000, type=int, help="Min community weight to create governance")
@click.pass_context
def create_realm(ctx, name, community_mint, council_mint, min_weight):
    """Create a new realm.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body: dict = {
        "fee_payer": payer,
        "name": name,
        "community_mint": community_mint,
        "realm_authority": payer,
        "min_community_weight_to_create_governance": min_weight,
    }
    if council_mint:
        body["council_mint"] = council_mint

    client = _get_client(opts)
    tx_resp = _api_call(client.tx_create_realm, body)
    success(_sign_send(opts, tx_resp))


@cli.command("create-governance")
@click.option("--realm", "realm_addr", default=None, help="Realm address [from config]")
@click.option("--governed-account", required=True, help="Account to govern")
@click.option("--vote-threshold", default=60, type=int, help="Community vote threshold percentage")
@click.option("--voting-time", default=259200, type=int, help="Voting time in seconds (default 3 days)")
@click.pass_context
def create_governance(ctx, realm_addr, governed_account, vote_threshold, voting_time):
    """Create a new governance.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    if realm_addr:
        opts["realm"] = realm_addr
    r = _require(opts, "realm", "realm")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    from solders.pubkey import Pubkey

    tor, _ = find_token_owner_record(
        Pubkey.from_string(opts["program_id"]),
        Pubkey.from_string(r),
        Pubkey.from_string(mint),
        kp.pubkey(),
    )

    body = {
        "fee_payer": payer,
        "realm": r,
        "governed_account": governed_account,
        "token_owner_record": str(tor),
        "realm_authority": payer,
        "community_vote_threshold_percentage": vote_threshold,
        "min_community_weight_to_create_proposal": 1,
        "voting_base_time": voting_time,
        "transactions_hold_up_time": 0,
    }

    client = _get_client(opts)
    tx_resp = _api_call(client.tx_create_governance, body)
    success(_sign_send(opts, tx_resp))


@cli.command("create-treasury")
@click.option("--governance", default=None, help="Governance address [from config]")
@click.pass_context
def create_treasury(ctx, governance):
    """Create a native treasury for a governance.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    if governance:
        opts["governance"] = governance
    gov = _require(opts, "governance", "governance")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {"fee_payer": payer, "governance": gov}
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_create_native_treasury, body)
    success(_sign_send(opts, tx_resp))


# ---------------------------------------------------------------------------
# Participant commands (signing required)
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--realm", "realm_addr", default=None, help="Realm address [from config]")
@click.option("--source", required=True, help="Token source account")
@click.option("--amount", required=True, type=int, help="Amount to deposit")
@click.pass_context
def deposit(ctx, realm_addr, source, amount):
    """Deposit governing tokens.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    if realm_addr:
        opts["realm"] = realm_addr
    r = _require(opts, "realm", "realm")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {
        "fee_payer": payer,
        "realm": r,
        "governing_token_mint": mint,
        "governing_token_source": source,
        "governing_token_owner": payer,
        "amount": amount,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_deposit_tokens, body)
    success(_sign_send(opts, tx_resp))


@cli.command()
@click.option("--realm", "realm_addr", default=None, help="Realm address [from config]")
@click.option("--destination", required=True, help="Token destination account")
@click.pass_context
def withdraw(ctx, realm_addr, destination):
    """Withdraw governing tokens.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    if realm_addr:
        opts["realm"] = realm_addr
    r = _require(opts, "realm", "realm")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {
        "fee_payer": payer,
        "realm": r,
        "governing_token_mint": mint,
        "governing_token_owner": payer,
        "destination": destination,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_withdraw_tokens, body)
    success(_sign_send(opts, tx_resp))


@cli.command()
@click.option("--governance", default=None, help="Governance address [from config]")
@click.option("--name", required=True, help="Proposal name")
@click.option("--description", "description_link", default="", help="Description link")
@click.pass_context
def propose(ctx, governance, name, description_link):
    """Create a proposal.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    if governance:
        opts["governance"] = governance
    gov = _require(opts, "governance", "governance")
    r = _require(opts, "realm", "realm")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    from solders.pubkey import Pubkey
    from solders.keypair import Keypair as SoldersKeypair

    program_id = Pubkey.from_string(opts["program_id"])
    tor, _ = find_token_owner_record(
        program_id,
        Pubkey.from_string(r),
        Pubkey.from_string(mint),
        kp.pubkey(),
    )

    # Generate a random proposal seed
    proposal_seed = SoldersKeypair()

    body = {
        "fee_payer": payer,
        "realm": r,
        "governance": gov,
        "token_owner_record": str(tor),
        "governing_token_mint": mint,
        "governance_authority": payer,
        "name": name,
        "description_link": description_link,
        "options": ["Approve"],
        "use_deny_option": True,
        "proposal_seed": str(proposal_seed.pubkey()),
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_create_proposal, body)
    success(_sign_send(opts, tx_resp))


@cli.command("sign-off")
@click.option("--proposal", required=True, help="Proposal address")
@click.pass_context
def sign_off(ctx, proposal):
    """Sign off a proposal to move it to voting.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    r = _require(opts, "realm", "realm")
    gov = _require(opts, "governance", "governance")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {
        "fee_payer": payer,
        "realm": r,
        "governance": gov,
        "proposal": proposal,
        "signatory": payer,
        "governing_token_mint": mint,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_sign_off_proposal, body)
    success(_sign_send(opts, tx_resp))


@cli.command()
@click.option("--proposal", required=True, help="Proposal address")
@click.option("--proposal-owner-record", required=True, help="Proposal owner's TOR address")
@click.option("--choice", required=True, type=click.Choice(["approve", "deny", "abstain", "veto"]), help="Vote choice")
@click.pass_context
def vote(ctx, proposal, proposal_owner_record, choice):
    """Cast a vote on a proposal.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    r = _require(opts, "realm", "realm")
    gov = _require(opts, "governance", "governance")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    from solders.pubkey import Pubkey

    program_id = Pubkey.from_string(opts["program_id"])
    voter_tor, _ = find_token_owner_record(
        program_id,
        Pubkey.from_string(r),
        Pubkey.from_string(mint),
        kp.pubkey(),
    )

    body = {
        "fee_payer": payer,
        "realm": r,
        "governance": gov,
        "proposal": proposal,
        "proposal_owner_record": proposal_owner_record,
        "voter_token_owner_record": str(voter_tor),
        "governance_authority": payer,
        "governing_token_mint": mint,
        "vote": choice,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_cast_vote, body)
    success(_sign_send(opts, tx_resp))


@cli.command()
@click.option("--token-owner-record", required=True, help="TokenOwnerRecord address")
@click.option("--new-delegate", required=True, help="New delegate pubkey")
@click.pass_context
def delegate(ctx, token_owner_record, new_delegate):
    """Set a governance delegate.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {
        "fee_payer": payer,
        "current_delegate_or_owner": payer,
        "token_owner_record": token_owner_record,
        "new_delegate": new_delegate,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_set_governance_delegate, body)
    success(_sign_send(opts, tx_resp))


# ---------------------------------------------------------------------------
# Executor commands (signing required)
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--proposal", required=True, help="Proposal address")
@click.option("--proposal-transaction", required=True, help="ProposalTransaction address")
@click.option("--instruction-program-id", required=True, help="Program ID of the instruction to execute")
@click.pass_context
def execute(ctx, proposal, proposal_transaction, instruction_program_id):
    """Execute a proposal transaction.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    gov = _require(opts, "governance", "governance")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {
        "fee_payer": payer,
        "governance": gov,
        "proposal": proposal,
        "proposal_transaction": proposal_transaction,
        "instruction_program_id": instruction_program_id,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_execute_transaction, body)
    success(_sign_send(opts, tx_resp))


@cli.command()
@click.option("--proposal", required=True, help="Proposal address")
@click.option("--proposal-owner-record", required=True, help="Proposal owner's TOR address")
@click.pass_context
def finalize(ctx, proposal, proposal_owner_record):
    """Finalize a vote on a proposal.

    Output: {"ok": true, "data": {"signature": "..."}}
    """
    opts = _resolve_opts(ctx)
    r = _require(opts, "realm", "realm")
    gov = _require(opts, "governance", "governance")
    mint = _require(opts, "governing_token_mint", "governing-token-mint")

    kp = load_keypair(opts["keypair"])
    payer = str(kp.pubkey())

    body = {
        "fee_payer": payer,
        "realm": r,
        "governance": gov,
        "proposal": proposal,
        "proposal_owner_record": proposal_owner_record,
        "governing_token_mint": mint,
    }
    client = _get_client(opts)
    tx_resp = _api_call(client.tx_finalize_vote, body)
    success(_sign_send(opts, tx_resp))


# ---------------------------------------------------------------------------
# Phase 2 stubs: Formation & Payments
# ---------------------------------------------------------------------------

@cli.group(invoke_without_command=True)
@click.option("--non-interactive", is_flag=True, help="Dump JSON schema instead of interactive Q&A")
@click.option("--form-type", default="entity-formation", help="Form type [default: entity-formation]")
@click.option("--no-hints", is_flag=True, help="Hide FAQ hints during interactive flow")
@click.pass_context
def form(ctx, non_interactive, form_type, no_hints):
    """Interactive formation forms — step-by-step Q&A or JSON schema mode.

    \b
    Modes:
      corpo form                       Interactive Q&A (default)
      corpo form --non-interactive     Dump JSON schema for programmatic use
      corpo form post '<json>'         Validate and submit JSON data
      corpo form faq                   Show legal FAQ hints
    """
    ctx.ensure_object(dict)
    ctx.obj["form_type"] = form_type
    ctx.obj["no_hints"] = no_hints

    if ctx.invoked_subcommand is not None:
        return

    from corpo.forms.engine import FormEngine

    try:
        engine = FormEngine(form_type)
    except ValueError as e:
        error(str(e))

    if non_interactive:
        # JSON schema mode
        import json as _json
        schema = engine.dump_schema()
        click.echo(_json.dumps(schema, indent=2))
        return

    # Interactive mode
    answers = engine.run_interactive(show_hints=not no_hints)
    if answers is None:
        error("Form cancelled.")

    # Submit via quote flow
    result = engine.submit(answers)
    success(result)


@form.command("post")
@click.argument("json_data")
@click.option("--form-type", default="entity-formation", help="Form type [default: entity-formation]")
@click.pass_context
def form_post(ctx, json_data, form_type):
    """Validate and submit form data as JSON.

    \b
    Example:
      corpo form post '{"entity_name": "Alpine LAO LLC", "entity_type": "dao_llc", ...}'

    Output: {"ok": true, "data": {"valid": true, "quote": {...}}}
    """
    import json as _json
    from corpo.forms.engine import FormEngine

    try:
        data = _json.loads(json_data)
    except _json.JSONDecodeError as e:
        error(f"Invalid JSON: {e}")

    try:
        engine = FormEngine(form_type)
    except ValueError as e:
        error(str(e))

    result = engine.submit(data)
    if not result.get("valid", False):
        # Return validation errors
        import sys
        _json.dump({"ok": False, "errors": result.get("errors", [])}, sys.stdout, indent=2)
        sys.stdout.write("\n")
        sys.exit(1)

    success(result)


@form.command("faq")
@click.option("--field", default=None, help="Show hints for a specific field (e.g., 'name', 'tax', 'vote')")
@click.pass_context
def form_faq(ctx, field):
    """Show legal FAQ hints relevant to formation fields.

    Output: {"ok": true, "data": {"hints": [...]}}
    """
    from corpo.faq_hints import lookup_hints, _HINTS

    if field:
        hints = lookup_hints(field, max_results=10)
        if not hints:
            error(f"No FAQ hints for field: {field}", hint="Try: name, tax, vote, member, cost, dissolution")
        success({"hints": [{"question": h.question, "answer": h.short_answer, "statutory_reference": h.statutory_reference, "category": h.category} for h in hints]})
    else:
        # Show all unique hints
        seen = set()
        all_hints = []
        for _, hint in _HINTS:
            if hint.question not in seen:
                seen.add(hint.question)
                all_hints.append({"question": hint.question, "answer": hint.short_answer, "statutory_reference": hint.statutory_reference, "category": hint.category})
        success({"hints": all_hints})


@form.command("schema")
@click.option("--form-type", default="entity-formation", help="Form type [default: entity-formation]")
@click.pass_context
def form_schema(ctx, form_type):
    """Dump the full form definition (sections, fields, validation rules).

    Output: {"ok": true, "data": {...form definition...}}
    """
    from corpo.forms.engine import FormEngine

    try:
        engine = FormEngine(form_type)
    except ValueError as e:
        error(str(e))

    success(engine.dump_definition())


@cli.command("quote")
@click.argument("name")
@click.option("--entity-type", default="dao_llc", type=click.Choice(["llc", "dao_llc"]), help="Entity type [default: dao_llc]")
@click.pass_context
def quote(ctx, name, entity_type):
    """Get a formation pricing quote for an entity name.

    Checks name availability and returns a pricing breakdown.
    The returned quote_id can be used with `corpo pay --quote-id <id>`.

    Output: {"ok": true, "data": {"quote_id": "...", "entity_name": "...", "pricing": {...}}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first or set CORPO_API_KEY")
    client = CorpoClient(api_url, api_key=api_key)
    result = _api_call(client.create_quote, name, entity_type)

    # Display pricing table
    if result.get("name_available"):
        pricing = result.get("pricing", {})
        click.echo("")
        click.echo(f"  Entity:  {result.get('entity_name', name)}")
        click.echo(f"  Type:    {result.get('entity_type', entity_type)}")
        click.echo(f"  Status:  Name available ✓")
        click.echo("")
        click.echo("  Pricing Breakdown:")
        click.echo(f"    Filing fee (WY SOS)      ${pricing.get('filing_fee', 0) / 100:>10,.2f}")
        click.echo(f"    Corpo service fee         ${pricing.get('service_fee', 0) / 100:>10,.2f}")
        click.echo(f"    Registered agent (yr 1)   ${pricing.get('registered_agent', 0) / 100:>10,.2f}")
        click.echo(f"    {'─' * 34}")
        click.echo(f"    Total                     ${pricing.get('total_cents', 0) / 100:>10,.2f}")
        click.echo("")
        click.echo(f"  Quote ID: {result.get('quote_id', 'N/A')}")
        click.echo(f"  Next:     corpo pay --quote-id {result.get('quote_id', '<id>')}")
        click.echo("")
    else:
        click.echo("")
        click.echo(f"  Entity:  {result.get('entity_name', name)}")
        click.echo(f"  Status:  Name unavailable ✗")
        click.echo("")

    success(result)


# ---------------------------------------------------------------------------
# Register
# ---------------------------------------------------------------------------

@cli.command()
@click.option("--email", required=True, help="Email address for account registration")
@click.pass_context
def register(ctx, email):
    """Register a new account and store the API key.

    Output: {"ok": true, "data": {"account_id": "...", "config_path": "..."}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    client = CorpoClient(api_url)
    result = _api_call(client.register_account, email)
    # Store credentials
    cfg["api_key"] = result.get("api_key", "")
    cfg["account_id"] = result.get("account_id", result.get("id", ""))
    save_auth_config(cfg)
    success({
        "account_id": cfg["account_id"],
        "config_path": "~/.corpo/config.json",
        "message": "Registered! API key saved to ~/.corpo/config.json",
    })


# ---------------------------------------------------------------------------
# Auth group
# ---------------------------------------------------------------------------

@cli.group()
def auth():
    """Manage API authentication."""
    pass


@auth.command("set-key")
@click.argument("key")
def auth_set_key(key):
    """Manually set your API key.

    Output: {"ok": true, "data": {"message": "API key saved"}}
    """
    cfg = load_auth_config()
    cfg["api_key"] = key
    save_auth_config(cfg)
    success({"message": "API key saved to ~/.corpo/config.json"})


@auth.command("status")
def auth_status():
    """Check if the current API key is valid.

    Output: {"ok": true, "data": {"authenticated": true, "account_id": "...", "account": {...}}}
    """
    cfg = load_auth_config()
    key = get_api_key(cfg)
    if not key:
        error("No API key configured", hint="Run `corpo register` first or set CORPO_API_KEY")
    account_id = cfg.get("account_id", "")
    if not account_id:
        error("No account_id in config", hint="Run `corpo register` or set account_id via `corpo config set account_id <value>`")
    api_url = get_api_url(cfg)
    client = CorpoClient(api_url, api_key=key)
    result = _api_call(client.get_account, account_id)
    success({
        "authenticated": True,
        "account_id": account_id,
        "account": result,
    })


# ---------------------------------------------------------------------------
# Config group
# ---------------------------------------------------------------------------

@cli.group("config")
def config_group():
    """Manage CLI configuration."""
    pass


@config_group.command("show")
def config_show():
    """Show all configuration values.

    Output: {"ok": true, "data": {"api_url": "...", "api_key": "abc...xyz", ...}}
    """
    cfg = load_auth_config()
    # Mask API key for display
    display = dict(cfg)
    if display.get("api_key"):
        k = display["api_key"]
        display["api_key"] = k[:6] + "..." + k[-4:] if len(k) > 10 else "***"
    success(display)


@config_group.command("get")
@click.argument("key")
def config_get(key):
    """Get a single configuration value.

    Output: {"ok": true, "data": {"<key>": "<value>"}}
    """
    if key not in VALID_KEYS:
        error(f"Unknown config key: {key}", hint=f"Valid keys: {', '.join(sorted(VALID_KEYS))}")
    cfg = load_auth_config()
    success({key: cfg.get(key, "")})


@config_group.command("set")
@click.argument("key")
@click.argument("value")
def config_set(key, value):
    """Set a configuration value.

    Output: {"ok": true, "data": {"message": "Set <key>", "<key>": "<value>"}}
    """
    if key not in VALID_KEYS:
        error(f"Unknown config key: {key}", hint=f"Valid keys: {', '.join(sorted(VALID_KEYS))}")
    cfg = load_auth_config()
    cfg[key] = value
    save_auth_config(cfg)
    success({"message": f"Set {key}", key: value})


@cli.command("pay")
@click.option("--quote-id", required=True, help="Quote ID from 'corpo quote'")
@click.option("--method", default="credit_card", type=click.Choice(["credit_card", "usdc_solana"]), help="Payment method")
@click.pass_context
def pay(ctx, quote_id, method):
    """Pay a formation quote. Triggers formation submission.

    Output: {"ok": true, "data": {"quote_id": "...", "payment_id": "...", "status": "..."}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first or set CORPO_API_KEY")
    client = CorpoClient(api_url, api_key=api_key)
    result = _api_call(client.pay_quote, quote_id, method)
    success(result)


# ---------------------------------------------------------------------------
# Corporate Actions
# ---------------------------------------------------------------------------

@cli.group("actions")
@click.pass_context
def actions_group(ctx):
    """Corporate actions — governance moves for your entity.

    Commands: corpo actions list, corpo actions start, corpo actions status
    """
    pass


@actions_group.command("catalog")
@click.pass_context
def actions_catalog(ctx):
    """Show the full catalog of corporate action types.

    Output: {"ok": true, "data": {"actions": [...]}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    client = CorpoClient(api_url, api_key=api_key or "")
    result = _api_call(client.get_actions_catalog)
    # Pretty print
    if isinstance(result, list):
        click.echo("")
        click.echo("  Corporate Actions Catalog")
        click.echo("  " + "=" * 50)
        for a in result:
            click.echo(f"  [{a.get('id', '?')}] {a.get('name', '?')}")
            click.echo(f"      Type: {a.get('type', '?')}")
            click.echo(f"      {a.get('description', '')}")
            forms = a.get("required_forms", [])
            if forms:
                click.echo(f"      Forms: {', '.join(forms)}")
            if a.get("requires_vote"):
                click.echo(f"      ⚡ Requires governance vote")
            click.echo("")
    success({"actions": result})


@actions_group.command("list")
@click.argument("entity_id")
@click.pass_context
def actions_list(ctx, entity_id):
    """List available and active actions for an entity.

    Output: {"ok": true, "data": {"available": [...], "active": [...]}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first")
    client = CorpoClient(api_url, api_key=api_key)

    available = _api_call(client.list_available_actions, entity_id)
    active = _api_call(client.list_actions, entity_id)

    click.echo("")
    click.echo(f"  Available Actions for {entity_id}")
    click.echo("  " + "-" * 40)
    if available:
        for a in available:
            vote = " ⚡vote" if a.get("requires_vote") else ""
            click.echo(f"  [{a.get('id', '?')}] {a.get('name', '?')}{vote}")
    else:
        click.echo("  (none available)")

    if active:
        click.echo("")
        click.echo(f"  Active Actions")
        click.echo("  " + "-" * 40)
        for a in active:
            click.echo(f"  {a.get('id', '?')[:8]}... {a.get('action_type', '?')} → {a.get('status', '?')}")
    click.echo("")

    success({"available": available or [], "active": active or []})


@actions_group.command("start")
@click.argument("entity_id")
@click.argument("action_type")
@click.option("--params", "params_json", default=None, help="JSON params string")
@click.option("--non-interactive", is_flag=True, help="Skip interactive prompts")
@click.pass_context
def actions_start(ctx, entity_id, action_type, params_json, non_interactive):
    """Start a corporate action for an entity.

    Output: {"ok": true, "data": {"action": {...}}}
    """
    import json as json_mod

    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first")
    client = CorpoClient(api_url, api_key=api_key)

    params = None
    if params_json:
        try:
            params = json_mod.loads(params_json)
        except json_mod.JSONDecodeError as e:
            error(f"Invalid JSON params: {e}")

    if not non_interactive and not params:
        # Fetch catalog to show required params
        catalog = _api_call(client.get_actions_catalog)
        action_info = None
        if catalog:
            for a in catalog:
                if a.get("type") == action_type:
                    action_info = a
                    break

        if action_info and action_info.get("params_schema"):
            params = {}
            click.echo(f"\n  Starting: {action_info.get('name', action_type)}")
            click.echo(f"  {action_info.get('description', '')}\n")
            for name, schema in action_info["params_schema"].items():
                req = " (required)" if schema.get("required") else " (optional)"
                desc = schema.get("description", "")
                val = click.prompt(f"  {name}{req} — {desc}", default="" if not schema.get("required") else None)
                if val:
                    params[name] = val
            click.echo("")

    result = _api_call(client.initiate_action, entity_id, action_type, params)
    click.echo(f"  ✓ Action initiated: {result.get('id', '?')}")
    click.echo(f"    Type: {result.get('action_type', '?')}")
    click.echo(f"    Status: {result.get('status', '?')}")
    click.echo("")
    success({"action": result})


@actions_group.command("status")
@click.argument("entity_id")
@click.argument("action_id")
@click.option("--forms", is_flag=True, help="Also render and show required forms")
@click.pass_context
def actions_status(ctx, entity_id, action_id, forms):
    """Check the status of a corporate action.

    Output: {"ok": true, "data": {"action": {...}, "forms": [...]}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first")
    client = CorpoClient(api_url, api_key=api_key)

    action = _api_call(client.get_action, entity_id, action_id)
    click.echo(f"\n  Action: {action.get('id', '?')}")
    click.echo(f"  Type: {action.get('action_type', '?')}")
    click.echo(f"  Status: {action.get('status', '?')}")
    click.echo(f"  Created: {action.get('created_at', '?')}")
    click.echo("")

    rendered_forms = None
    if forms:
        rendered_forms = _api_call(client.get_action_forms, entity_id, action_id)
        if rendered_forms:
            for f in rendered_forms:
                click.echo(f"  --- {f.get('template_name', 'form')} ---")
                click.echo(f.get("content", ""))
                click.echo("")

    data = {"action": action}
    if rendered_forms is not None:
        data["forms"] = rendered_forms
    success(data)


@actions_group.command("execute")
@click.argument("entity_id")
@click.argument("action_id")
@click.pass_context
def actions_execute(ctx, entity_id, action_id):
    """Advance a corporate action to the next lifecycle stage.

    Output: {"ok": true, "data": {"action": {...}}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first")
    client = CorpoClient(api_url, api_key=api_key)

    result = _api_call(client.execute_action, entity_id, action_id)
    click.echo(f"  ✓ Action advanced: {result.get('id', '?')[:8]}...")
    click.echo(f"    Status: {result.get('status', '?')}")
    click.echo("")
    success({"action": result})


# ---------------------------------------------------------------------------
# Recipes
# ---------------------------------------------------------------------------

@cli.group("recipes")
@click.pass_context
def recipes_group(ctx):
    """Recipes — pre-built sequences of corporate actions.

    Commands: corpo recipes list, corpo recipes show, corpo recipes run
    """
    pass


@recipes_group.command("list")
@click.pass_context
def recipes_list(ctx):
    """List all available recipes.

    Output: {"ok": true, "data": {"recipes": [...]}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    client = CorpoClient(api_url, api_key=api_key or "")
    result = _api_call(client.list_recipes)

    if isinstance(result, list):
        click.echo("")
        click.echo("  Available Recipes")
        click.echo("  " + "=" * 50)
        for r in result:
            click.echo(f"  📋 {r.get('name', '?')} [{r.get('id', '?')}]")
            click.echo(f"     {r.get('description', '')}")
            steps = r.get("steps", [])
            for i, step in enumerate(steps, 1):
                opt = " (optional)" if step.get("optional") else ""
                click.echo(f"     {i}. {step.get('action_type', '?')} — {step.get('description', '')}{opt}")
            tags = r.get("tags", [])
            if tags:
                click.echo(f"     Tags: {', '.join(tags)}")
            click.echo("")

    success({"recipes": result})


@recipes_group.command("show")
@click.argument("recipe_id")
@click.pass_context
def recipes_show(ctx, recipe_id):
    """Show details of a specific recipe.

    Output: {"ok": true, "data": {"recipe": {...}}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    client = CorpoClient(api_url, api_key=api_key or "")
    result = _api_call(client.get_recipe, recipe_id)

    click.echo(f"\n  📋 {result.get('name', '?')} [{result.get('id', '?')}]")
    click.echo(f"  Category: {result.get('category', '?')}")
    click.echo(f"  Version: {result.get('version', '?')}")
    click.echo(f"  {result.get('description', '')}\n")
    click.echo("  Steps:")
    for i, step in enumerate(result.get("steps", []), 1):
        opt = " (optional)" if step.get("optional") else ""
        click.echo(f"    {i}. [{step.get('action_type', '?')}] {step.get('description', '')}{opt}")
    click.echo("")

    success({"recipe": result})


@recipes_group.command("run")
@click.argument("entity_id")
@click.argument("recipe_id")
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
@click.pass_context
def recipes_run(ctx, entity_id, recipe_id, yes):
    """Execute a recipe against an entity.

    Output: {"ok": true, "data": {"execution": {...}}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first")
    client = CorpoClient(api_url, api_key=api_key)

    # Show recipe before executing
    recipe = _api_call(client.get_recipe, recipe_id)
    click.echo(f"\n  📋 Running: {recipe.get('name', recipe_id)}")
    click.echo(f"  Entity: {entity_id}")
    click.echo(f"  Steps:")
    for i, step in enumerate(recipe.get("steps", []), 1):
        opt = " (optional)" if step.get("optional") else ""
        click.echo(f"    {i}. {step.get('action_type', '?')} — {step.get('description', '')}{opt}")
    click.echo("")

    if not yes:
        if not click.confirm("  Proceed?"):
            error("Cancelled.")

    result = _api_call(client.execute_recipe, entity_id, recipe_id)

    click.echo(f"\n  Execution status: {result.get('status', '?')}")
    for sr in result.get("step_results", []):
        icon = "✓" if sr.get("status") == "initiated" else "⊘" if sr.get("status") == "skipped" else "✗"
        click.echo(f"    {icon} Step {sr.get('step_index', '?')}: {sr.get('action_type', '?')} → {sr.get('status', '?')}")
        if sr.get("action_id"):
            click.echo(f"      Action ID: {sr['action_id']}")
        if sr.get("error"):
            click.echo(f"      Error: {sr['error']}")
    click.echo("")

    success({"execution": result})


@cli.group("governance")
@click.pass_context
def governance_group(ctx):
    """Governance — on-chain governance integration.

    Commands: corpo governance setup
    """
    pass


@governance_group.command("setup")
@click.argument("entity_id")
@click.argument("realm_address")
@click.argument("governance_program_id")
@click.pass_context
def governance_setup(ctx, entity_id, realm_address, governance_program_id):
    """Record a Realms governance address on an entity.

    Output: {"ok": true, "data": {"governance": {...}}}
    """
    cfg = load_auth_config()
    api_url = get_api_url(cfg)
    api_key = get_api_key(cfg)
    if not api_key:
        error("No API key configured", hint="Run `corpo register` first")
    client = CorpoClient(api_url, api_key=api_key)

    result = _api_call(client.setup_governance, entity_id, realm_address, governance_program_id)

    click.echo(f"\n  ✓ Governance recorded for entity {entity_id}")
    click.echo(f"    Realm: {result.get('realm_address', '?')}")
    click.echo(f"    Program: {result.get('governance_program_id', '?')}")
    click.echo("")

    success({"governance": result})


if __name__ == "__main__":
    cli()
