"""Configuration management for odin-bots.

Loads configuration from odin-bots.toml in the project root.
"""

import os
from pathlib import Path
from typing import Optional

import tomllib


# ---------------------------------------------------------------------------
# Network & canister constants
# ---------------------------------------------------------------------------

IC_HOST = "https://ic0.app"

# onicai ckSigner canister IDs per PoAIW network
CKSIGNER_CANISTER_IDS = {
    "prd": "g7qkb-iiaaa-aaaar-qb3za-cai",
    "testing": "ho2u6-qaaaa-aaaar-qb34q-cai",
    "development": "ho2u6-qaaaa-aaaar-qb34q-cai",
}
VALID_NETWORKS = list(CKSIGNER_CANISTER_IDS.keys())

# Default (prd) — kept for backward compatibility
ONICAI_CKSIGNER_CANISTER_ID = CKSIGNER_CANISTER_IDS["prd"]

# Odin.fun SIWB canister (Sign-In with Bitcoin)
ODIN_SIWB_CANISTER_ID = "bcxqa-kqaaa-aaaak-qotba-cai"

# ckBTC ledger
CKBTC_LEDGER_CANISTER_ID = "mxzaz-hqaaa-aaaar-qaada-cai"

# ckBTC minter (BTC <-> ckBTC)
CKBTC_MINTER_CANISTER_ID = "mqygn-kiaaa-aaaar-qaadq-cai"

# Odin.fun trading canister
ODIN_TRADING_CANISTER_ID = "z2vm5-gaaaa-aaaaj-azw6q-cai"

# Odin.fun ckBTC deposit helper
ODIN_DEPOSIT_CANISTER_ID = "ztwhb-qiaaa-aaaaj-azw7a-cai"

# Odin.fun API
ODIN_API_URL = "https://api.odin.fun/v1"

# ---------------------------------------------------------------------------
# Trading limits (enforced by Odin.fun trading canister)
# ---------------------------------------------------------------------------

MIN_DEPOSIT_SATS = 5000   # minimum ckBTC deposit into Odin.Fun
MIN_TRADE_SATS = 500      # minimum BTC-equivalent for buy/sell on Odin.Fun
MIN_BTC_WITHDRAWAL_SATS = 50_000  # minimum BTC withdrawal via ckBTC minter

# ---------------------------------------------------------------------------
# BTC/USD rate & sats formatting
# ---------------------------------------------------------------------------

def get_btc_to_usd_rate() -> float:
    """Get the current BTC to USD exchange rate from Coinbase."""
    import requests
    url = "https://api.coinbase.com/v2/exchange-rates?currency=BTC"
    response = requests.get(url, timeout=30)
    response.raise_for_status()
    data = response.json()
    return float(data["data"]["rates"]["USD"])


def fmt_sats(sats, btc_usd_rate) -> str:
    """Format sats with optional USD value.

    Args:
        sats: Amount in satoshis.
        btc_usd_rate: BTC/USD rate, or None to skip USD.
    """
    if btc_usd_rate:
        usd = (sats / 100_000_000) * btc_usd_rate
        return f"{sats:,} sats (${usd:.2f})"
    return f"{sats:,} sats"


# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------

# PEM file path (always .wallet/identity-private.pem, generated by: odin-bots wallet create)
PEM_FILE = ".wallet/identity-private.pem"

DEFAULT_CONFIG = {
    "settings": {"default_persona": "iconfucius"},
    "bots": {
        "bot-1": {"description": "Bot 1"},
    },
    "ai": {},
}

CONFIG_FILENAME = "odin-bots.toml"

# Module-level cache
_cached_config: Optional[dict] = None
_cached_config_path: Optional[Path] = None

# Module-level verbose flag (controlled by CLI --verbose in the future)
_verbose: bool = True


def set_verbose(enabled: bool) -> None:
    """Set the global verbose flag."""
    global _verbose
    _verbose = enabled


def is_verbose() -> bool:
    """Return the current verbose flag."""
    return _verbose


def log(msg: str) -> None:
    """Print a message if verbose mode is enabled."""
    if _verbose:
        print(msg)


# Module-level network (default: prd)
_network: str = "prd"


def set_network(network: str) -> None:
    """Set the active PoAIW network."""
    global _network
    if network not in CKSIGNER_CANISTER_IDS:
        raise ValueError(f"Unknown network '{network}'. Valid: {VALID_NETWORKS}")
    _network = network


def get_network() -> str:
    """Return the active PoAIW network."""
    return _network


def get_cksigner_canister_id() -> str:
    """Return the ckSigner canister ID for the active network."""
    return CKSIGNER_CANISTER_IDS[_network]


def _project_root() -> str:
    """Return the odin_bots project root directory.

    Resolution order:
    1. ODIN_BOTS_ROOT environment variable (if set)
    2. Current working directory
    """
    return os.environ.get("ODIN_BOTS_ROOT", os.environ.get("PWD", os.getcwd()))


def find_config() -> Optional[Path]:
    """Find odin-bots.toml in cwd or ODIN_BOTS_ROOT.

    Returns:
        Path to config file if found, None otherwise.
    """
    root = Path(_project_root())
    config_path = root / CONFIG_FILENAME
    if config_path.exists():
        return config_path
    return None


def load_config(reload: bool = False) -> dict:
    """Load config from odin-bots.toml or return defaults.

    Args:
        reload: If True, reload config even if cached.

    Returns:
        Configuration dictionary with settings and bots.
    """
    global _cached_config, _cached_config_path

    if _cached_config is not None and not reload:
        return _cached_config

    config_path = find_config()

    if config_path is None:
        _cached_config = DEFAULT_CONFIG.copy()
        _cached_config_path = None
        return _cached_config

    with open(config_path, "rb") as f:
        config = tomllib.load(f)

    # Merge with defaults for missing keys
    result = DEFAULT_CONFIG.copy()
    if "settings" in config:
        result["settings"] = {**DEFAULT_CONFIG["settings"], **config["settings"]}
    if "bots" in config:
        result["bots"] = config["bots"]
    if "ai" in config:
        result["ai"] = config["ai"]

    _cached_config = result
    _cached_config_path = config_path
    return result


def get_config_path() -> Optional[Path]:
    """Return the path to the loaded config file, or None if using defaults."""
    load_config()  # Ensure config is loaded
    return _cached_config_path


def get_pem_file() -> str:
    """Return the absolute path to the PEM file."""
    return os.path.join(_project_root(), PEM_FILE)


def require_wallet() -> bool:
    """Check that the wallet PEM exists. Print instructions and return False if not."""
    pem_path = get_pem_file()
    if os.path.exists(pem_path):
        return True
    print("No odin-bots wallet found in current directory. Set it up first:\n")
    print("  odin-bots wallet create")
    print("  odin-bots wallet receive   # shows how to fund your odin bots")
    print()
    print(f"  (expected PEM at {pem_path})")
    return False


def get_cache_sessions() -> bool:
    """Return whether session caching to disk is enabled.

    Reads `cache_sessions` from [settings] in odin-bots.toml.
    Defaults to True. When False, sessions are not written to disk
    and a fresh SIWB login is performed for every command.
    """
    config = load_config()
    return config.get("settings", {}).get("cache_sessions", True)


def get_verify_certificates() -> bool:
    """Return whether IC certificate verification is enabled.

    Reads `verify_certificates` from [settings] in odin-bots.toml.
    Defaults to False (blst not required for basic usage).
    When True, requires the blst library to be installed.
    """
    config = load_config()
    enabled = config.get("settings", {}).get("verify_certificates", False)
    if enabled:
        try:
            import blst  # noqa: F401
        except ImportError:
            print("Error: verify_certificates = true in odin-bots.toml,")
            print("but the 'blst' library is not installed.")
            print()
            print("See README-security.md for installation instructions.")
            raise SystemExit(1)
    return enabled


def get_bot_names() -> list[str]:
    """Return list of configured bot names."""
    config = load_config()
    return list(config["bots"].keys())


def get_bot_description(bot_name: str) -> str:
    """Return the description for a bot, or empty string if not found."""
    config = load_config()
    bot = config["bots"].get(bot_name, {})
    return bot.get("description", "")


def validate_bot_name(name: str) -> bool:
    """Check if bot name exists in config.

    Note: Any non-empty string is valid as a bot name (derivation path).
    This just checks if it's explicitly configured.
    """
    config = load_config()
    return name in config["bots"]


def get_default_persona() -> str:
    """Return the default persona name from config."""
    config = load_config()
    return config.get("settings", {}).get("default_persona", "iconfucius")


def get_ai_config() -> dict:
    """Return project-level [ai] config (overrides persona AI settings)."""
    config = load_config()
    return config.get("ai", {})


def get_bot_persona(bot_name: str) -> str:
    """Return the persona assigned to a bot, or default persona."""
    config = load_config()
    bot = config["bots"].get(bot_name, {})
    return bot.get("persona", get_default_persona())


def create_default_config(num_bots: int = 3) -> str:
    """Generate default config file content.

    Args:
        num_bots: Number of bot definitions to create (1-1000, default 3).

    Returns:
        TOML content as string.
    """
    num_bots = max(1, min(1000, num_bots))
    header = '''# odin-bots configuration
# See: https://github.com/onicai/odin_bots

[settings]
# See README-security.md for details
verify_certificates = false
cache_sessions = true
default_persona = "iconfucius"

# AI backend (overrides persona defaults)
# API key via env var: ANTHROPIC_API_KEY, GEMINI_API_KEY, etc.
# [ai]
# backend = "claude"
# model = "claude-sonnet-4-5-20250929"

# Bot definitions
# Each bot gets its own trading identity on Odin.Fun.
# Optional: persona = "<name>" assigns a trading persona to the bot.
'''
    bots = "\n".join(
        f'[bots.bot-{i}]\ndescription = "Bot {i}"\n'
        for i in range(1, num_bots + 1)
    )
    return header + bots


def add_bots_to_config(current_max: int, target: int) -> list[str]:
    """Append new bot sections to odin-bots.toml.

    Args:
        current_max: Highest existing bot number (e.g. 10 if bot-10 exists).
        target: Desired total bot count.

    Returns:
        List of newly added bot names.
    """
    import re as _re

    config_path = Path(CONFIG_FILENAME)
    content = config_path.read_text()
    new_names = []
    new_sections = []
    for i in range(current_max + 1, target + 1):
        name = f"bot-{i}"
        new_names.append(name)
        new_sections.append(f'[bots.{name}]\ndescription = "Bot {i}"\n')
    if new_sections:
        separator = "" if content.endswith("\n") else "\n"
        content += separator + "\n".join(new_sections)
        config_path.write_text(content)
    return new_names


def remove_bots_from_config(bot_names: list[str]) -> None:
    """Remove bot sections from odin-bots.toml.

    Args:
        bot_names: List of bot names to remove (e.g. ["bot-8", "bot-9"]).
    """
    import re as _re

    config_path = Path(CONFIG_FILENAME)
    content = config_path.read_text()
    for name in bot_names:
        # Match [bots.<name>] and everything until next [section] or EOF
        pattern = rf'\[bots\.{_re.escape(name)}\]\n(?:[^\[]*)'
        content = _re.sub(pattern, '', content)
    # Clean up triple+ blank lines
    content = _re.sub(r'\n{3,}', '\n\n', content)
    config_path.write_text(content)
