"""Encrypted token vault and config — multi-account, PIN-protected write tokens.

Each Cloudflare account gets its own namespace:
  - Read token: plaintext in config.json (read-only, low risk)
  - Write token: encrypted in {account}.vault.enc (PIN-protected)

Secrets are collected via a one-shot localhost web form that the AI
never sees. See prompt.py for details.

Flow:
  1. Setup: `clawdflare setup` → browser wizard → all fields at once
  2. Read op: config file → token used directly
  3. Write op: browser PIN form → decrypt vault → API call → discard token
"""

import base64
import hashlib
import json
import os
import platform
from pathlib import Path

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

VAULT_DIR = Path.home() / ".config" / "clawdflare"
VAULT_FILE = VAULT_DIR / "vault.enc"
CONFIG_FILE = VAULT_DIR / "config.json"

MIN_PIN_LENGTH = 6
DEFAULT_ACCOUNT = "default"


def _vault_file(account: str) -> Path:
    """Return the vault file path for a specific account."""
    if account == DEFAULT_ACCOUNT:
        return VAULT_DIR / "vault.enc"
    return VAULT_DIR / f"{account}.vault.enc"


# ---------------------------------------------------------------------------
# Config — plaintext read tokens + account management
# ---------------------------------------------------------------------------

def _load_config() -> dict:
    if not CONFIG_FILE.exists():
        return {}
    return json.loads(CONFIG_FILE.read_text())


def _save_config(config: dict) -> None:
    VAULT_DIR.mkdir(parents=True, exist_ok=True)
    CONFIG_FILE.write_text(json.dumps(config, indent=2))
    if platform.system() != "Windows":
        os.chmod(CONFIG_FILE, 0o600)


def save_read_token(
    token: str,
    account: str = DEFAULT_ACCOUNT,
    token_name: str | None = None,
) -> None:
    """Save the read-only API token for an account."""
    config = _load_config()
    if "accounts" not in config:
        config["accounts"] = {}
    if account not in config["accounts"]:
        config["accounts"][account] = {}
    config["accounts"][account]["read_token"] = token
    if token_name:
        config["accounts"][account]["read_token_name"] = token_name

    if "active" not in config or len(config["accounts"]) == 1:
        config["active"] = account

    _save_config(config)


def get_read_token(account: str | None = None) -> str | None:
    """Load the read-only API token for an account. Falls back to active account."""
    config = _load_config()
    if not account:
        account = config.get("active", DEFAULT_ACCOUNT)
    accounts = config.get("accounts", {})
    return accounts.get(account, {}).get("read_token")


def get_active_account() -> str:
    config = _load_config()
    return config.get("active", DEFAULT_ACCOUNT)


def set_active_account(account: str) -> None:
    config = _load_config()
    accounts = config.get("accounts", {})
    if account not in accounts:
        raise RuntimeError(
            f"Account '{account}' not found. "
            f"Available: {', '.join(accounts.keys()) or 'none — run clawdflare setup'}"
        )
    config["active"] = account
    _save_config(config)


def list_accounts() -> list[dict]:
    """List all configured accounts (no secrets exposed)."""
    config = _load_config()
    active = config.get("active", DEFAULT_ACCOUNT)
    accounts = config.get("accounts", {})

    return [
        {
            "name": name,
            "active": name == active,
            "has_read_token": bool(acct.get("read_token")),
            "read_token_name": acct.get("read_token_name", "—"),
            "has_write_vault": _vault_file(name).exists(),
            "write_token_name": acct.get("write_token_name", "—"),
        }
        for name, acct in accounts.items()
    ]


def config_exists(account: str | None = None) -> bool:
    return get_read_token(account) is not None


# ---------------------------------------------------------------------------
# Vault — encrypted write tokens
# ---------------------------------------------------------------------------

def _derive_key(pin: str, salt: bytes) -> bytes:
    return hashlib.pbkdf2_hmac("sha256", pin.encode(), salt, iterations=600_000)


def encrypt_token(
    token: str,
    pin: str,
    account: str = DEFAULT_ACCOUNT,
    token_name: str | None = None,
) -> None:
    """Encrypt a Cloudflare API token with a PIN and save to disk."""
    salt = os.urandom(16)
    key = _derive_key(pin, salt)
    nonce = os.urandom(12)
    aesgcm = AESGCM(key)
    ciphertext = aesgcm.encrypt(nonce, token.encode(), None)

    payload = {
        "salt": base64.b64encode(salt).decode(),
        "nonce": base64.b64encode(nonce).decode(),
        "ciphertext": base64.b64encode(ciphertext).decode(),
    }

    vf = _vault_file(account)
    VAULT_DIR.mkdir(parents=True, exist_ok=True)
    vf.write_text(json.dumps(payload))

    if platform.system() != "Windows":
        os.chmod(vf, 0o600)

    if token_name:
        config = _load_config()
        if "accounts" not in config:
            config["accounts"] = {}
        if account not in config["accounts"]:
            config["accounts"][account] = {}
        config["accounts"][account]["write_token_name"] = token_name
        _save_config(config)


def _decrypt_token(pin: str, account: str | None = None) -> str:
    """Decrypt the stored token using PIN. Raises on wrong PIN or missing vault."""
    if not account:
        account = get_active_account()

    vf = _vault_file(account)
    if not vf.exists():
        raise RuntimeError(
            f"No encrypted token found for account '{account}'. "
            "Run `clawdflare setup` first."
        )

    payload = json.loads(vf.read_text())
    salt = base64.b64decode(payload["salt"])
    nonce = base64.b64decode(payload["nonce"])
    ciphertext = base64.b64decode(payload["ciphertext"])

    key = _derive_key(pin, salt)
    aesgcm = AESGCM(key)

    try:
        plaintext = aesgcm.decrypt(nonce, ciphertext, None)
    except Exception:
        raise RuntimeError("Wrong PIN or corrupted vault.")

    return plaintext.decode()


def vault_exists(account: str | None = None) -> bool:
    if not account:
        account = get_active_account()
    return _vault_file(account).exists()


# ---------------------------------------------------------------------------
# High-level operations (delegate to prompt.py for UI)
# ---------------------------------------------------------------------------

def interactive_setup(account: str | None = None) -> dict:
    """Run the full interactive setup via browser wizard.

    Opens a one-shot localhost page that collects account name, token names,
    tokens, and PIN — all in one form. The AI never sees any of it.
    """
    from clawdflare.lib.prompt import prompt_setup_wizard

    data = prompt_setup_wizard(accounts_exist=bool(list_accounts()))

    account = data.get("account", DEFAULT_ACCOUNT).strip() or DEFAULT_ACCOUNT
    pin = data["pin"]

    if len(pin) < MIN_PIN_LENGTH:
        raise RuntimeError(
            f"PIN must be at least {MIN_PIN_LENGTH} characters. "
            "A 4-digit PIN can be brute-forced in ~8 minutes."
        )

    # Save read token
    save_read_token(
        data["read_token"],
        account,
        token_name=data.get("read_token_name"),
    )

    # Encrypt write token
    encrypt_token(
        data["write_token"],
        pin,
        account,
        token_name=data.get("write_token_name"),
    )

    # Wipe secrets from memory
    for key in ("read_token", "write_token", "pin"):
        data[key] = "[redacted]"

    return {
        "account": account,
        "read_token": f"saved to {CONFIG_FILE}",
        "write_token": f"encrypted to {_vault_file(account)}",
        "status": "ready",
    }


def authorize_write(account: str | None = None) -> str:
    """Prompt for PIN via browser, decrypt token, return it.

    The returned token should be used immediately for a single API call
    and then discarded. Never store it, log it, or return it to the caller
    above the tools layer.
    """
    if not account:
        account = get_active_account()

    from clawdflare.lib.prompt import prompt_secret

    pin = prompt_secret(
        "clawdflare authorization",
        f"Enter PIN for account <strong>{account}</strong> to authorize this write operation.",
        label="PIN",
    )
    token = _decrypt_token(pin, account)
    del pin
    return token
