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


DEFAULT_CONFIG_FILENAME = "agent-config.json"
DEFAULT_CONFIG_PATHS = [
    Path.cwd() / DEFAULT_CONFIG_FILENAME,
    Path.home() / ".config" / "agent-config" / DEFAULT_CONFIG_FILENAME,
]


def load_config(config_path: Optional[str] = None) -> dict[str, Any]:
    """Load configuration from a JSON file.

    Args:
        config_path: Optional explicit path to config file. If None, searches default locations.

    Returns:
        Dictionary containing the configuration.

    Raises:
        FileNotFoundError: If no configuration file is found.
        json.JSONDecodeError: If the configuration file contains invalid JSON.
    """
    if config_path:
        path = Path(config_path)
        if not path.exists():
            raise FileNotFoundError(f"Configuration file not found: {config_path}")
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)

    for path in DEFAULT_CONFIG_PATHS:
        if path.exists():
            with open(path, "r", encoding="utf-8") as f:
                return json.load(f)

    return {}


def save_config(config: dict[str, Any], config_path: Optional[str] = None) -> str:
    """Save configuration to a JSON file.

    Args:
        config: Dictionary containing the configuration to save.
        config_path: Optional path to save the config file. Defaults to current directory.

    Returns:
        The path where the configuration was saved.
    """
    path = Path(config_path) if config_path else DEFAULT_CONFIG_PATHS[0]
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        json.dump(config, f, indent=2, sort_keys=True)
    return str(path)


def get_config_value(config: dict[str, Any], key: str) -> Any:
    """Retrieve a value from the configuration using dot-notation keys.

    Args:
        config: The configuration dictionary.
        key: Dot-separated key path (e.g., 'agent.model.name').

    Returns:
        The value at the specified key path.

    Raises:
        KeyError: If the key path does not exist in the configuration.
    """
    parts = key.split(".")
    current: Any = config
    for part in parts:
        if not isinstance(current, dict) or part not in current:
            raise KeyError(f"Configuration key not found: {key}")
        current = current[part]
    return current


def set_config_value(config: dict[str, Any], key: str, value: Any) -> dict[str, Any]:
    """Set a value in the configuration using dot-notation keys.

    Args:
        config: The configuration dictionary.
        key: Dot-separated key path (e.g., 'agent.model.name').
        value: The value to set.

    Returns:
        The updated configuration dictionary.
    """
    parts = key.split(".")
    current = config
    for part in parts[:-1]:
        if part not in current or not isinstance(current.get(part), dict):
            current[part] = {}
        current = current[part]
    current[parts[-1]] = value
    return config


def list_tools(config: dict[str, Any]) -> list[dict[str, Any]]:
    """List all configured AI agent tools.

    Args:
        config: The configuration dictionary.

    Returns:
        A list of tool definitions from the configuration.
    """
    tools = config.get("tools", [])
    if not isinstance(tools, list):
        return []
    return tools


def validate_config(config: dict[str, Any]) -> list[str]:
    """Validate the configuration and return a list of issues.

    Args:
        config: The configuration dictionary to validate.

    Returns:
        A list of validation error messages. Empty list means valid.
    """
    errors: list[str] = []
    if not isinstance(config, dict):
        errors.append("Configuration must be a JSON object")
        return errors

    if "tools" in config:
        if not isinstance(config["tools"], list):
            errors.append("'tools' must be an array")
        else:
            for i, tool in enumerate(config["tools"]):
                if not isinstance(tool, dict):
                    errors.append(f"Tool at index {i} must be an object")
                elif "name" not in tool:
                    errors.append(f"Tool at index {i} is missing required 'name' field")

    return errors


def format_output(data: Any, as_json: bool = False) -> str:
    """Format data for display.

    Args:
        data: The data to format.
        as_json: If True, output as JSON string.

    Returns:
        Formatted string representation of the data.
    """
    if as_json:
        return json.dumps(data, indent=2, sort_keys=True, default=str)
    if isinstance(data, dict):
        lines = []
        for k, v in data.items():
            lines.append(f"{k}: {v}")
        return "\n".join(lines)
    if isinstance(data, list):
        return "\n".join(str(item) for item in data)
    return str(data)


def _parse_value(raw: str) -> Any:
    """Attempt to parse a string value as JSON, falling back to string."""
    try:
        return json.loads(raw)
    except (json.JSONDecodeError, ValueError):
        return raw


def build_cli_parser() -> argparse.ArgumentParser:
    """Build and return the CLI argument parser.

    Returns:
        Configured ArgumentParser instance.
    """
    parser = argparse.ArgumentParser(
        prog="agent-config",
        description="AI Agent Tools configuration manager",
    )
    parser.add_argument("--config", "-c", type=str, default=None, help="Path to config file")
    parser.add_argument("--json", dest="as_json", action="store_true", help="Output as JSON")

    sub = parser.add_subparsers(dest="command", help="Available commands")

    sub.add_parser("init", help="Initialize a new configuration file")
    sub.add_parser("show", help="Show current configuration")
    sub.add_parser("validate", help="Validate configuration")
    sub.add_parser("list-tools", help="List configured tools")

    get_p = sub.add_parser("get", help="Get a configuration value")
    get_p.add_argument("key", help="Dot-notation key path")

    set_p = sub.add_parser("set", help="Set a configuration value")
    set_p.add_argument("key", help="Dot-notation key path")
    set_p.add_argument("value", help="Value to set (parsed as JSON if possible)")

    return parser


def cli_main(argv: Optional[list[str]] = None) -> int:
    """Main CLI entry point.

    Args:
        argv: Optional list of command-line arguments. Defaults to sys.argv[1:].

    Returns:
        Exit code (0 for success, non-zero for errors).
    """
    parser = build_cli_parser()
    args = parser.parse_args(argv)

    if not args.command:
        parser.print_help()
        return 0

    try:
        if args.command == "init":
            default = {"tools": [], "settings": {}}
            saved = save_config(default, args.config)
            print(format_output({"message": f"Config initialized at {saved}"}, args.as_json))
            return 0

        config = load_config(args.config)

        if args.command == "show":
            print(format_output(config, args.as_json))
        elif args.command == "validate":
            errors = validate_config(config)
            if errors:
                print(format_output({"valid": False, "errors": errors}, args.as_json))
                return 1
            print(format_output({"valid": True, "errors": []}, args.as_json))
        elif args.command == "list-tools":
            tools = list_tools(config)
            print(format_output(tools, args.as_json))
        elif args.command == "get":
            value = get_config_value(config, args.key)
            print(format_output(value, args.as_json))
        elif args.command == "set":
            parsed_value = _parse_value(args.value)
            config = set_config_value(config, args.key, parsed_value)
            save_config(config, args.config)
            print(format_output({"message": f"Set {args.key}", "value": parsed_value}, args.as_json))
        else:
            parser.print_help()

    except (FileNotFoundError, KeyError, json.JSONDecodeError) as e:
        error_msg = {"error": type(e).__name__, "message": str(e)}
        print(format_output(error_msg, args.as_json), file=sys.stderr)
        return 1
    except Exception as e:
        error_msg = {"error": "UnexpectedError", "message": str(e)}
        print(format_output(error_msg, args.as_json), file=sys.stderr)
        return 2

    return 0


if __name__ == "__main__":
    sys.exit(cli_main())