#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#

"""Command-line interface for the Veculo SDK.

Usage::

    veculo connect --endpoint https://api.veculo.com --api-key vk-... --cluster-id cl-a7f3b2
    veculo status
    veculo put-vertex --id alice --label person --property name=Alice
    veculo get-vertex --id alice
    veculo put-edge --source alice --target bob --type knows
    veculo query --embedding "0.1,0.2,0.3" --top-k 10
"""

from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any

_CONFIG_DIR = Path.home() / ".veculo"
_CONFIG_FILE = _CONFIG_DIR / "config.json"


# ------------------------------------------------------------------
# Config helpers
# ------------------------------------------------------------------


def _load_config() -> dict[str, str]:
    """Load saved configuration from ``~/.veculo/config.json``."""
    if _CONFIG_FILE.exists():
        try:
            return json.loads(_CONFIG_FILE.read_text())
        except (json.JSONDecodeError, OSError):
            return {}
    return {}


def _save_config(config: dict[str, str]) -> None:
    """Persist configuration to ``~/.veculo/config.json``."""
    _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    _CONFIG_FILE.write_text(json.dumps(config, indent=2) + "\n")


def _resolve(key: str, args: argparse.Namespace, config: dict[str, str]) -> str | None:
    """Resolve a value from CLI args, then config file, then env vars."""
    cli_val = getattr(args, key, None)
    if cli_val:
        return cli_val
    if key in config:
        return config[key]
    env_map = {
        "endpoint": "VECULO_ENDPOINT",
        "api_key": "VECULO_API_KEY",
        "cluster_id": "VECULO_CLUSTER_ID",
        "cluster_name": "VECULO_CLUSTER_NAME",
    }
    return os.environ.get(env_map.get(key, ""))


def _get_client(args: argparse.Namespace) -> Any:
    """Build a VeculoClient from resolved configuration."""
    from veculo.client import VeculoClient

    config = _load_config()
    endpoint = _resolve("endpoint", args, config)
    api_key = _resolve("api_key", args, config)
    cluster_id = _resolve("cluster_id", args, config)
    cluster_name = _resolve("cluster_name", args, config)
    return VeculoClient(
        endpoint=endpoint,
        api_key=api_key,
        cluster_id=cluster_id,
        cluster_name=cluster_name,
    )


def _print_json(data: Any) -> None:
    """Pretty-print a JSON-serializable object to stdout."""
    print(json.dumps(data, indent=2))


# ------------------------------------------------------------------
# Subcommand handlers
# ------------------------------------------------------------------


def _cmd_connect(args: argparse.Namespace) -> None:
    """Save connection details to ``~/.veculo/config.json``."""
    config: dict[str, str] = {}
    if args.endpoint:
        config["endpoint"] = args.endpoint
    if args.api_key:
        config["api_key"] = args.api_key
    if args.cluster_id:
        config["cluster_id"] = args.cluster_id
    if args.cluster_name:
        config["cluster_name"] = args.cluster_name
    _save_config(config)
    print(f"Configuration saved to {_CONFIG_FILE}")


def _cmd_status(args: argparse.Namespace) -> None:
    """Print cluster metrics / health."""
    client = _get_client(args)
    _print_json(client.get_metrics())


def _cmd_put_vertex(args: argparse.Namespace) -> None:
    """Insert or update a vertex."""
    client = _get_client(args)
    properties = _parse_properties(args.property) if args.property else None
    embedding = _parse_embedding(args.embedding) if args.embedding else None
    result = client.put_vertex(
        id=args.id,
        label=args.label or "default",
        properties=properties,
        embedding=embedding,
        visibility=args.visibility,
    )
    _print_json(result)


def _cmd_get_vertex(args: argparse.Namespace) -> None:
    """Retrieve a vertex by ID."""
    client = _get_client(args)
    result = client.get_vertex(id=args.id, authorizations=args.authorizations)
    _print_json(result)


def _cmd_put_edge(args: argparse.Namespace) -> None:
    """Create an edge between two vertices."""
    client = _get_client(args)
    properties = _parse_properties(args.property) if args.property else None
    result = client.put_edge(
        source=args.source,
        target=args.target,
        edge_type=args.type,
        properties=properties,
        visibility=args.visibility,
    )
    _print_json(result)


def _cmd_query(args: argparse.Namespace) -> None:
    """Execute a hybrid query."""
    client = _get_client(args)
    embedding = _parse_embedding(args.embedding) if args.embedding else None
    result = client.query(
        embedding=embedding,
        top_k=args.top_k,
        edge_type=args.edge_type,
        depth=args.depth,
        authorizations=args.authorizations,
    )
    _print_json(result)


# ------------------------------------------------------------------
# Argument parsing helpers
# ------------------------------------------------------------------


def _parse_properties(pairs: list[str]) -> dict[str, str]:
    """Parse ``key=value`` pairs into a dictionary."""
    props: dict[str, str] = {}
    for pair in pairs:
        if "=" not in pair:
            print(f"Warning: ignoring malformed property '{pair}' (expected key=value)", file=sys.stderr)
            continue
        key, _, value = pair.partition("=")
        props[key] = value
    return props


def _parse_embedding(raw: str) -> list[float]:
    """Parse a comma-separated string of floats into a list."""
    return [float(x.strip()) for x in raw.split(",")]


# ------------------------------------------------------------------
# Main entry point
# ------------------------------------------------------------------


def _build_parser() -> argparse.ArgumentParser:
    """Construct the argument parser tree."""
    parser = argparse.ArgumentParser(
        prog="veculo",
        description="Veculo CLI -- managed graph+vector database",
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # -- connect -------------------------------------------------------
    p_connect = subparsers.add_parser("connect", help="Save connection configuration")
    p_connect.add_argument("--endpoint", help="API endpoint URL")
    p_connect.add_argument("--api-key", dest="api_key", help="Veculo API key")
    p_connect.add_argument("--cluster-id", dest="cluster_id", help="Cluster ID (e.g., cl-a7f3b2)")
    p_connect.add_argument("--cluster", dest="cluster_name", help="Cluster name (e.g., 'production')")

    # -- status --------------------------------------------------------
    subparsers.add_parser("status", help="Show cluster status and metrics")

    # -- put-vertex ----------------------------------------------------
    p_pv = subparsers.add_parser("put-vertex", help="Insert or update a vertex")
    p_pv.add_argument("--id", required=True, help="Vertex ID")
    p_pv.add_argument("--label", default="default", help="Vertex label")
    p_pv.add_argument(
        "--property", action="append", metavar="KEY=VALUE", help="Vertex property (repeatable)"
    )
    p_pv.add_argument("--embedding", help="Comma-separated vector embedding")
    p_pv.add_argument("--visibility", help="Visibility expression")

    # -- get-vertex ----------------------------------------------------
    p_gv = subparsers.add_parser("get-vertex", help="Retrieve a vertex by ID")
    p_gv.add_argument("--id", required=True, help="Vertex ID")
    p_gv.add_argument("--authorizations", help="Visibility authorizations")

    # -- put-edge ------------------------------------------------------
    p_pe = subparsers.add_parser("put-edge", help="Create an edge between two vertices")
    p_pe.add_argument("--source", required=True, help="Source vertex ID")
    p_pe.add_argument("--target", required=True, help="Target vertex ID")
    p_pe.add_argument("--type", required=True, help="Edge type")
    p_pe.add_argument(
        "--property", action="append", metavar="KEY=VALUE", help="Edge property (repeatable)"
    )
    p_pe.add_argument("--visibility", help="Visibility expression")

    # -- query ---------------------------------------------------------
    p_q = subparsers.add_parser("query", help="Execute a hybrid vector+graph query")
    p_q.add_argument("--embedding", help="Comma-separated query vector")
    p_q.add_argument("--top-k", dest="top_k", type=int, default=10, help="Number of results")
    p_q.add_argument("--edge-type", dest="edge_type", help="Edge type filter for graph expansion")
    p_q.add_argument("--depth", type=int, default=1, help="Graph traversal depth")
    p_q.add_argument("--authorizations", help="Visibility authorizations")

    return parser


def main(argv: list[str] | None = None) -> None:
    """CLI entry point."""
    parser = _build_parser()
    args = parser.parse_args(argv)

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

    handlers = {
        "connect": _cmd_connect,
        "status": _cmd_status,
        "put-vertex": _cmd_put_vertex,
        "get-vertex": _cmd_get_vertex,
        "put-edge": _cmd_put_edge,
        "query": _cmd_query,
    }

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

    try:
        handler(args)
    except Exception as exc:
        print(f"Error: {exc}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
