#!/usr/bin/env python3
"""CLI for mesh credential vault management.

Usage:
    mesh-vault init
    mesh-vault unseal
    mesh-vault seal
    mesh-vault add --provider aws --service model_storage --scope global
    mesh-vault list
    mesh-vault get <credential_id>
    mesh-vault rotate <credential_id>
    mesh-vault delete <credential_id>
    mesh-vault grant <credential_id> --node <node_id>
    mesh-vault revoke <credential_id> --node <node_id>
    mesh-vault audit [credential_id]
    mesh-vault guide --provider aws --service model_storage
    mesh-vault export --encrypted <file>
    mesh-vault import --encrypted <file>
"""
from __future__ import annotations

import argparse
import getpass
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path


def _get_db_path() -> str:
    """Resolve the vault DB path from config or default."""
    config_path = Path("mesh/mesh_config.yaml")
    if config_path.exists():
        try:
            from mesh.config import MeshConfig
            cfg = MeshConfig.from_yaml(str(config_path))
            if hasattr(cfg, "vault") and hasattr(cfg.vault, "db_path"):
                return cfg.vault.db_path
        except Exception:
            pass
    return "data/mesh_vault.db"


def _get_passphrase(env_var: str = "MESH_VAULT_PASSPHRASE", prompt: str = "Vault passphrase: ") -> str:
    """Read passphrase from env var or interactive prompt."""
    passphrase = os.environ.get(env_var, "")
    if not passphrase:
        passphrase = getpass.getpass(prompt)
    if not passphrase:
        print("Error: passphrase must not be empty", file=sys.stderr)
        sys.exit(1)
    return passphrase


def _open_store(passphrase: str | None = None):
    """Open the vault store, prompting for passphrase if needed."""
    from mesh.vault.crypto import VaultCrypto
    from mesh.vault.store import VaultStore

    if passphrase is None:
        passphrase = _get_passphrase()
    db_path = _get_db_path()
    crypto = VaultCrypto(passphrase)
    return VaultStore(db_path, crypto)


def cmd_init(args):
    """Initialize the vault with a new master passphrase."""
    db_path = _get_db_path()
    if Path(db_path).exists():
        confirm = input(f"Vault DB already exists at {db_path}. Overwrite? [y/N] ")
        if confirm.lower() != "y":
            print("Aborted.")
            return

    passphrase = getpass.getpass("Set master passphrase: ")
    if not passphrase:
        print("Error: passphrase must not be empty", file=sys.stderr)
        sys.exit(1)
    confirm = getpass.getpass("Confirm passphrase: ")
    if passphrase != confirm:
        print("Error: passphrases do not match", file=sys.stderr)
        sys.exit(1)

    store = _open_store(passphrase)
    store.close()
    print(f"Vault initialized at {db_path}")
    print("Set MESH_VAULT_PASSPHRASE env var for non-interactive usage.")


def cmd_add(args):
    """Add a new credential to the vault."""
    from mesh.vault.models import CloudProvider, CredentialRequest, CredentialScope, ServiceRequirement
    from mesh.vault.policies import get_policy

    store = _open_store()

    try:
        provider = CloudProvider(args.provider)
    except ValueError:
        print(f"Error: unknown provider '{args.provider}'", file=sys.stderr)
        print(f"Valid: {', '.join(p.value for p in CloudProvider)}")
        sys.exit(1)

    try:
        service = ServiceRequirement(args.service)
    except ValueError:
        print(f"Error: unknown service '{args.service}'", file=sys.stderr)
        print(f"Valid: {', '.join(s.value for s in ServiceRequirement)}")
        sys.exit(1)

    try:
        scope = CredentialScope(args.scope)
    except ValueError:
        scope = CredentialScope.GLOBAL

    # Show policy guide if available
    policy = get_policy(provider, service)
    if policy:
        print(f"\n--- Required fields: {', '.join(policy.required_fields)}")
        if policy.optional_fields:
            print(f"--- Optional fields: {', '.join(policy.optional_fields)}")
        print()

    # Collect fields interactively
    fields: dict[str, str] = {}
    if policy:
        for field_name in policy.required_fields:
            value = getpass.getpass(f"  {field_name}: ")
            if not value:
                print(f"Error: {field_name} is required", file=sys.stderr)
                sys.exit(1)
            fields[field_name] = value
        for field_name in policy.optional_fields:
            value = input(f"  {field_name} (optional, Enter to skip): ")
            if value:
                fields[field_name] = value
    else:
        print("No policy found. Enter fields as key=value (empty line to finish):")
        while True:
            line = input("  > ")
            if not line:
                break
            if "=" not in line:
                print("  Format: key=value")
                continue
            k, v = line.split("=", 1)
            fields[k.strip()] = v.strip()

    if not fields:
        print("Error: no fields provided", file=sys.stderr)
        sys.exit(1)

    name = args.name or f"{provider.value}-{service.value}"
    allowed_nodes = args.nodes.split(",") if args.nodes else []

    req = CredentialRequest(
        name=name,
        provider=provider,
        service=service,
        scope=scope,
        allowed_nodes=allowed_nodes,
        fields=fields,
    )
    cred_id = store.store(req)
    store.close()
    print(f"Credential stored: {cred_id}")


def cmd_list(args):
    """List all credentials (no secrets shown)."""
    store = _open_store()
    entries = store.list_credentials()
    store.close()

    if not entries:
        print("No credentials stored.")
        return

    print(f"{'ID':<34} {'Name':<25} {'Provider':<10} {'Service':<18} {'Scope':<12} {'Accessed':<20}")
    print("-" * 120)
    for e in entries:
        accessed = e.last_accessed.strftime("%Y-%m-%d %H:%M") if e.last_accessed else "never"
        print(
            f"{e.credential_id:<34} {e.name:<25} {e.provider.value:<10} "
            f"{e.service:<18} {e.scope.value:<12} {accessed:<20}"
        )


def cmd_get(args):
    """Show credential details (decrypted)."""
    store = _open_store()
    grant = store.retrieve(args.credential_id, "__controller__")
    store.close()

    if grant is None:
        print(f"Credential {args.credential_id} not found or access denied.", file=sys.stderr)
        sys.exit(1)

    print(f"Credential: {grant.credential_id}")
    print(f"Provider:   {grant.provider.value}")
    print(f"Grant ID:   {grant.grant_id}")
    print(f"Expires:    {grant.expires_at.isoformat()}")
    print("Fields:")
    for k, v in grant.fields.items():
        # Mask all but last 4 chars for secrets
        if len(v) > 8:
            masked = "*" * (len(v) - 4) + v[-4:]
        else:
            masked = "****"
        print(f"  {k}: {masked}")

    if args.show_plaintext:
        print("\n--- PLAINTEXT (sensitive!) ---")
        for k, v in grant.fields.items():
            print(f"  {k}: {v}")


def cmd_rotate(args):
    """Rotate credential values."""
    from mesh.vault.policies import get_policy
    from mesh.vault.models import CloudProvider, ServiceRequirement

    store = _open_store()

    # Look up existing credential to get provider/service for policy hints
    entries = store.list_credentials()
    entry = None
    for e in entries:
        if e.credential_id == args.credential_id:
            entry = e
            break

    if entry is None:
        print(f"Credential {args.credential_id} not found.", file=sys.stderr)
        store.close()
        sys.exit(1)

    policy = get_policy(entry.provider, ServiceRequirement(entry.service))
    new_fields: dict[str, str] = {}

    print(f"Rotating credential: {entry.name} ({entry.provider.value}/{entry.service})")
    if policy:
        for field_name in policy.required_fields:
            value = getpass.getpass(f"  New {field_name}: ")
            if value:
                new_fields[field_name] = value
    else:
        print("Enter new fields as key=value (empty line to finish):")
        while True:
            line = input("  > ")
            if not line:
                break
            if "=" in line:
                k, v = line.split("=", 1)
                new_fields[k.strip()] = v.strip()

    if not new_fields:
        print("No new values provided, skipping rotation.")
        store.close()
        return

    success = store.rotate(args.credential_id, new_fields)
    store.close()
    if success:
        print(f"Credential {args.credential_id} rotated.")
    else:
        print("Rotation failed.", file=sys.stderr)


def cmd_delete(args):
    """Delete a credential."""
    store = _open_store()
    confirm = input(f"Delete credential {args.credential_id}? [y/N] ")
    if confirm.lower() != "y":
        print("Aborted.")
        store.close()
        return
    success = store.delete(args.credential_id)
    store.close()
    if success:
        print(f"Credential {args.credential_id} deleted.")
    else:
        print("Credential not found.", file=sys.stderr)


def cmd_grant(args):
    """Grant a node access to a credential."""
    store = _open_store()
    success = store.grant_node(args.credential_id, args.node)
    store.close()
    if success:
        print(f"Node {args.node} granted access to {args.credential_id}")
    else:
        print("Credential not found.", file=sys.stderr)


def cmd_revoke(args):
    """Revoke a node's access to a credential."""
    store = _open_store()
    success = store.revoke_node(args.credential_id, args.node)
    store.close()
    if success:
        print(f"Node {args.node} access revoked from {args.credential_id}")
    else:
        print("Credential not found.", file=sys.stderr)


def cmd_audit(args):
    """Show access log."""
    store = _open_store()
    log = store.get_access_log(credential_id=args.credential_id, limit=args.limit)
    store.close()

    if not log:
        print("No access log entries.")
        return

    print(f"{'Timestamp':<26} {'Credential':<34} {'Node':<20} {'Action':<20} {'Granted':<8} {'IP'}")
    print("-" * 130)
    for entry in log:
        print(
            f"{entry.get('timestamp', ''):<26} {entry.get('credential_id', ''):<34} "
            f"{entry.get('node_id', '-'):<20} {entry.get('action', ''):<20} "
            f"{'yes' if entry.get('granted') else 'no':<8} {entry.get('ip_address', '-')}"
        )


def cmd_guide(args):
    """Show setup guide for a provider/service combination."""
    from mesh.vault.models import CloudProvider, ServiceRequirement
    from mesh.vault.policies import get_policy, get_policies_for_provider, POLICIES

    if args.provider and args.service:
        try:
            provider = CloudProvider(args.provider)
            service = ServiceRequirement(args.service)
        except ValueError as e:
            print(f"Error: {e}", file=sys.stderr)
            sys.exit(1)
        policy = get_policy(provider, service)
        if policy is None:
            print(f"No guide for {args.provider}/{args.service}")
            return
        print(policy.setup_guide)
    elif args.provider:
        try:
            provider = CloudProvider(args.provider)
        except ValueError:
            print(f"Unknown provider: {args.provider}", file=sys.stderr)
            sys.exit(1)
        policies = get_policies_for_provider(provider)
        for p in policies:
            print(f"--- {p.service.value}: {p.description}")
            print(f"    Required: {', '.join(p.required_fields)}")
            print()
    else:
        print("Available provider/service combinations:")
        for p in POLICIES:
            print(f"  {p.provider.value:<10} {p.service.value:<20} {p.description}")


def cmd_export(args):
    """Export credentials (encrypted) to a file."""
    store = _open_store()
    data = store.export_encrypted()
    store.close()

    with open(args.file, "w") as f:
        json.dump(data, f, indent=2)
    os.chmod(args.file, 0o600)
    print(f"Exported {len(data)} credentials to {args.file} (encrypted at rest)")


def cmd_import(args):
    """Import credentials from an encrypted export file."""
    store = _open_store()

    with open(args.file) as f:
        data = json.load(f)

    count = store.import_encrypted(data)
    store.close()
    print(f"Imported {count} credentials from {args.file}")


def main():
    parser = argparse.ArgumentParser(
        prog="mesh-vault",
        description="Mesh credential vault management",
    )
    sub = parser.add_subparsers(dest="command", help="Command")

    # init
    sub.add_parser("init", help="Initialize vault with master passphrase")

    # unseal / seal (informational -- actual unseal is at startup)
    sub.add_parser("unseal", help="Unseal vault (interactive)")
    sub.add_parser("seal", help="Seal vault")

    # add
    p_add = sub.add_parser("add", help="Add a credential")
    p_add.add_argument("--provider", required=True, help="Cloud provider (aws, gcp, azure, docker, github, custom)")
    p_add.add_argument("--service", required=True, help="Service requirement")
    p_add.add_argument("--scope", default="global", help="Scope (global, controller, node, service)")
    p_add.add_argument("--name", default="", help="Human-readable name")
    p_add.add_argument("--nodes", default="", help="Comma-separated node IDs (for scope=node)")

    # list
    sub.add_parser("list", help="List all credentials (no secrets)")

    # get
    p_get = sub.add_parser("get", help="Show credential details")
    p_get.add_argument("credential_id", help="Credential ID")
    p_get.add_argument("--show-plaintext", action="store_true", help="Show plaintext values")

    # rotate
    p_rotate = sub.add_parser("rotate", help="Rotate credential values")
    p_rotate.add_argument("credential_id", help="Credential ID")

    # delete
    p_del = sub.add_parser("delete", help="Delete a credential")
    p_del.add_argument("credential_id", help="Credential ID")

    # grant
    p_grant = sub.add_parser("grant", help="Grant node access to a credential")
    p_grant.add_argument("credential_id", help="Credential ID")
    p_grant.add_argument("--node", required=True, help="Node ID")

    # revoke
    p_revoke = sub.add_parser("revoke", help="Revoke node access to a credential")
    p_revoke.add_argument("credential_id", help="Credential ID")
    p_revoke.add_argument("--node", required=True, help="Node ID")

    # audit
    p_audit = sub.add_parser("audit", help="Show access audit log")
    p_audit.add_argument("credential_id", nargs="?", default=None, help="Credential ID (optional)")
    p_audit.add_argument("--limit", type=int, default=50, help="Max entries")

    # guide
    p_guide = sub.add_parser("guide", help="Show setup guide for a provider/service")
    p_guide.add_argument("--provider", default=None, help="Cloud provider")
    p_guide.add_argument("--service", default=None, help="Service requirement")

    # export
    p_export = sub.add_parser("export", help="Export credentials (encrypted)")
    p_export.add_argument("--encrypted", dest="file", required=True, help="Output file path")

    # import
    p_import = sub.add_parser("import", help="Import credentials from backup")
    p_import.add_argument("--encrypted", dest="file", required=True, help="Input file path")

    args = parser.parse_args()

    commands = {
        "init": cmd_init,
        "unseal": lambda a: print("Use MESH_VAULT_PASSPHRASE env var or start the MCP server to unseal."),
        "seal": lambda a: print("Seal is only meaningful for the running MCP server. Restart to re-seal."),
        "add": cmd_add,
        "list": cmd_list,
        "get": cmd_get,
        "rotate": cmd_rotate,
        "delete": cmd_delete,
        "grant": cmd_grant,
        "revoke": cmd_revoke,
        "audit": cmd_audit,
        "guide": cmd_guide,
        "export": cmd_export,
        "import": cmd_import,
    }

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    handler = commands.get(args.command)
    if handler is None:
        parser.print_help()
        sys.exit(1)

    handler(args)


if __name__ == "__main__":
    main()
