"""Project configuration for kv.

Handles .secrets/ directory, config.json, project discovery.
"""

import json
import os
from datetime import datetime, timezone

from .crypto import generate_master_key, save_key, save_wrapped_key


SECRETS_DIR = ".secrets"
CONFIG_FILE = "config.json"
KEY_FILE = "key"
GITIGNORE_FILE = ".gitignore"

GITIGNORE_CONTENT = """\
# kv — encrypted secrets management
# The key file must NEVER be committed
key

# Encrypted files are safe to commit
!*.enc
!config.json
"""


def find_project_root(start=None):
    """Walk up from start directory looking for .secrets/.

    Returns the project root (parent of .secrets/), or None.
    """
    path = os.path.abspath(start or os.getcwd())
    while True:
        secrets_dir = os.path.join(path, SECRETS_DIR)
        if os.path.isdir(secrets_dir):
            return path
        parent = os.path.dirname(path)
        if parent == path:
            return None
        path = parent


def secrets_dir(project_root):
    """Get the .secrets/ directory path."""
    return os.path.join(project_root, SECRETS_DIR)


def init_project(project_root=None, passphrase=None):
    """Initialize a new kv project.

    Creates .secrets/ with master key, config.json, .gitignore, and default dev env.
    If passphrase is provided, the master key is encrypted with it.
    Returns the project root path.
    """
    root = project_root or os.getcwd()
    sdir = os.path.join(root, SECRETS_DIR)

    if os.path.exists(sdir):
        raise FileExistsError(f"already initialized: {sdir}")

    os.makedirs(sdir)

    # Generate and save master key
    master = generate_master_key()
    kp = os.path.join(sdir, KEY_FILE)
    if passphrase:
        save_wrapped_key(master, passphrase, kp)
    else:
        save_key(master, kp)

    # Write config
    config = {
        "version": 1,
        "created": datetime.now(timezone.utc).isoformat(),
        "default_env": "dev",
        "environments": ["dev"],
        "cipher": "chacha20-poly1305",
        "security": {
            "passphrase": bool(passphrase),
            "totp": False,
        },
    }
    _write_config(sdir, config)

    # Write .gitignore
    with open(os.path.join(sdir, GITIGNORE_FILE), "w", encoding="utf-8") as f:
        f.write(GITIGNORE_CONTENT)

    return root


def load_config(project_root):
    """Load config.json from a project."""
    sdir = secrets_dir(project_root)
    config_path = os.path.join(sdir, CONFIG_FILE)
    with open(config_path, "r", encoding="utf-8") as f:
        return json.load(f)


def save_config(project_root, config):
    """Save config.json to a project."""
    sdir = secrets_dir(project_root)
    _write_config(sdir, config)


def add_environment(project_root, name):
    """Register a new environment in config."""
    config = load_config(project_root)
    if name in config["environments"]:
        return False
    config["environments"].append(name)
    save_config(project_root, config)
    return True


def list_environments(project_root):
    """Get all registered environment names."""
    config = load_config(project_root)
    return config["environments"]


def get_default_env(project_root):
    """Get the default environment name."""
    config = load_config(project_root)
    return config.get("default_env", "dev")


def key_path(project_root):
    """Get the path to the master key file."""
    return os.path.join(secrets_dir(project_root), KEY_FILE)


def get_run_policy(project_root):
    """Get kv_run security policy.

    Returns (policy, allowed_commands):
      policy: "allowlist" or "blocklist"
      allowed_commands: list of allowed command prefixes (for allowlist mode)
    """
    config = load_config(project_root)
    security = config.get("security", {})
    policy = security.get("kv_run_policy", "blocklist")
    allowed = security.get("allowed_commands", [])
    return policy, allowed


def is_network_allowed(project_root, argv):
    """Check if a command is allowed network access during kv_run.

    Network access is controlled by the user via config, not by the agent.
    Commands in the 'network_commands' list get network access; all others
    are network-isolated.

    Returns True if the command matches a network_commands entry.
    """
    config = load_config(project_root)
    security = config.get("security", {})
    net_commands = security.get("network_commands", [])
    if not net_commands:
        return False
    return _matches_list(argv, net_commands)


def add_network_command(project_root, command):
    """Allow network access for a command during kv_run.

    Returns True if added, False if already present.
    """
    config = load_config(project_root)
    security = config.setdefault("security", {})
    net_cmds = security.setdefault("network_commands", [])
    if command in net_cmds:
        return False
    net_cmds.append(command)
    save_config(project_root, config)
    return True


def _matches_list(argv, command_list):
    """Check if argv matches any entry in a command list (prefix matching).

    Delegates to sandbox._matches_allowlist — single implementation.
    """
    from .sandbox import _matches_allowlist
    return _matches_allowlist(argv, command_list)


def add_allowed_command(project_root, command):
    """Add a command to the kv_run allowlist.

    Automatically switches to allowlist policy on first add.
    Returns True if added, False if already present.
    """
    config = load_config(project_root)
    security = config.setdefault("security", {})
    allowed = security.setdefault("allowed_commands", [])
    if command in allowed:
        return False
    allowed.append(command)
    # Auto-switch to allowlist policy
    security["kv_run_policy"] = "allowlist"
    save_config(project_root, config)
    return True


def remove_allowed_command(project_root, command):
    """Remove a command from the kv_run allowlist.

    Returns True if removed, False if not found.
    """
    config = load_config(project_root)
    security = config.get("security", {})
    allowed = security.get("allowed_commands", [])
    if command not in allowed:
        return False
    allowed.remove(command)
    security["allowed_commands"] = allowed
    save_config(project_root, config)
    return True


def _write_config(sdir, config):
    """Write config.json atomically."""
    config_path = os.path.join(sdir, CONFIG_FILE)
    tmp_path = config_path + ".tmp"
    with open(tmp_path, "w", encoding="utf-8") as f:
        json.dump(config, f, indent=2)
        f.write("\n")
    os.replace(tmp_path, config_path)
