"""Core CLI infrastructure: MaerisGroup, env loading, cli group, and all sub-group definitions."""

import difflib
import json
import logging
import os
from pathlib import Path

import click
import structlog

from ._utils import _repo_config_dir


class MaerisCommand(click.Command):
    """Click Command with friendlier missing-argument errors."""

    def parse_args(self, ctx: click.Context, args: list) -> list:
        try:
            return super().parse_args(ctx, args)
        except click.NoSuchOption as exc:
            # Improve invalid option errors with a suggested flag + example
            invalid = getattr(exc, "option_name", "") or ""
            # Collect all option flags for this command
            opts: list[str] = []
            for p in self.params:
                if isinstance(p, click.Option):
                    opts.extend(p.opts or [])
                    opts.extend(p.secondary_opts or [])

            matches = difflib.get_close_matches(invalid, opts, n=1, cutoff=0.6)
            if matches:
                suggestion = matches[0]
                cmd_path = ctx.command_path
                extra = ""
                if suggestion == "--folder":
                    extra = (
                        "\nExample:\n"
                        f"  {cmd_path} --folder \"login\"\n"
                        f"  {cmd_path} --folder \"smoke\"\n"
                    )
                msg = (
                    f"No such option: {invalid}\n\n"
                    f"Did you mean {suggestion}?\n"
                    + extra
                )
                raise click.UsageError(msg)
            raise
        except click.MissingParameter as exc:
            # Improve the error for missing arguments (Click's default is terse)
            is_argument = (
                (exc.param and isinstance(exc.param, click.Argument))
                or exc.param_type == "argument"
            )
            if is_argument:
                param_name = (exc.param.human_readable_name if exc.param else "argument").upper()
                cmd_path = ctx.command_path  # e.g. "maeris app link"
                # Build a helpful usage line from the command's params
                parts = [cmd_path]
                for p in self.params:
                    if isinstance(p, click.Argument):
                        parts.append(f"<{p.human_readable_name}>")
                    elif isinstance(p, click.Option) and p.required:
                        parts.append(f"--{p.name} <value>")
                usage = " ".join(parts)
                usage_line = click.style(f"Usage: {usage}", fg="cyan")
                hint = click.style(f"Run '{cmd_path} --help' for full details.", fg="cyan")
                raise click.UsageError(
                    f"Missing required {param_name}.\n\n{usage_line}\n\n{hint}"
                )
            # Improve missing option value errors with concrete guidance
            if exc.param and isinstance(exc.param, click.Option):
                opt_name = exc.param.name or ""
                opt_flag = (exc.param.opts[0] if exc.param.opts else f"--{opt_name}")
                cmd_path = ctx.command_path
                if opt_name == "slow_mo":
                    msg = (
                        f"{opt_flag} expects an integer number of milliseconds.\n\n"
                        f"Example:\n"
                        f"  {cmd_path} --local {opt_flag} 800\n\n"
                        "Note: --slow-mo is only used with --local."
                    )
                    raise click.UsageError(msg)
            raise


class MaerisGroup(click.Group):
    """Click Group that suggests similar commands when the user makes a typo.

    Also uses MaerisCommand as the default command class so that all
    commands registered under this group get friendlier error messages.
    """

    command_class = MaerisCommand

    def resolve_command(self, ctx: click.Context, args: list) -> tuple:
        try:
            return super().resolve_command(ctx, args)
        except click.UsageError:
            cmd_name = args[0] if args else ""
            matches = difflib.get_close_matches(
                cmd_name,
                self.list_commands(ctx),
                n=3,
                cutoff=0.5,
            )
            hint = ""
            if matches:
                if len(matches) == 1:
                    hint = "\n\n" + click.style(f"Did you mean '{matches[0]}'?", fg="cyan")
                else:
                    opts = "  or  ".join(f"'{m}'" for m in matches)
                    hint = "\n\n" + click.style(f"Did you mean one of: {opts}?", fg="cyan")
            raise click.UsageError(f"No such command '{cmd_name}'.{hint}")


# Load .env file if it exists (for development)
def load_env_file() -> None:
    """Load CLI-level config from ~/.maeris/.env only.

    Intentionally does NOT read from the current working directory.
    The project's .env belongs to the repo being scanned — it may contain
    variables like MAERIS_APP_URL for unrelated purposes that would
    incorrectly override CLI settings.
    """
    try:
        from dotenv import load_dotenv

        cli_env = Path.home() / ".maeris" / ".env"
        if cli_env.exists():
            load_dotenv(cli_env, override=False)
    except ImportError:
        # python-dotenv not installed, skip
        pass


# Load .env on module import
load_env_file()


def _setup_cli_logging() -> None:
    """Suppress structlog console output for CLI commands.

    The MCP server configures its own logging in main.py.
    CLI commands should only show user-facing click.echo output,
    not raw structured log lines.
    """
    logging.basicConfig(level=logging.WARNING, handlers=[logging.NullHandler()])
    structlog.configure(
        wrapper_class=structlog.make_filtering_bound_logger(logging.WARNING),
    )


_setup_cli_logging()


_LEGACY_SERVER_NAMES = {"maeris-security", "maeris-mcp"}


def _write_mcp_json(
    config_dir: Path,
    maeris_scope: str | None = None,
    codebase_scope: str | None = None,
) -> None:
    """Write (or update) .mcp.json in the current directory.

    Sets MAERIS_CONFIG_DIR so the MCP server process loads the correct
    repo-specific credentials at runtime. Removes any legacy server entries
    (e.g. maeris-security, maeris) to avoid duplicate servers.

    Scope values are read at runtime from ``<MAERIS_CONFIG_DIR>/scope.json``.
    The optional scope args are accepted for backward-compatible callsites.
    """
    _ = maeris_scope, codebase_scope

    mcp_json_path = Path.cwd() / ".mcp.json"
    config = json.loads(mcp_json_path.read_text()) if mcp_json_path.exists() else {}
    servers = config.setdefault("mcpServers", {})

    # Remove stale entries from previous installs
    for legacy in _LEGACY_SERVER_NAMES:
        servers.pop(legacy, None)

    servers["maeris"] = {
        "command": "maeris",
        "args": ["serve"],
        "env": {
            "MAERIS_CONFIG_DIR": str(config_dir),
        },
    }
    mcp_json_path.write_text(json.dumps(config, indent=2))


@click.group(cls=MaerisGroup)
@click.version_option(package_name="maeris", prog_name="maeris")
def cli() -> None:
    """Maeris — Generate UI tests, extract APIs, and scan for security vulnerabilities from your code.

    \b
    Quick start:
      pip install maeris
      cd your-project && maeris app init
      Restart your editor, then ask your AI:
        "scan my repo for security issues"
        "extract APIs from my codebase"
        "generate UI tests for login flow"

    \b
    Authentication (two separate flows):
      maeris auth login   Connect this repo to Maeris Portal (API sync, test management)

    \b
    Troubleshooting:
      maeris doctor       Check your full setup and get fix instructions
    """
    # Point MaerisClient at the repo-specific credentials directory so CLI
    # commands find the same credentials that 'maeris login' wrote.
    # setdefault preserves MAERIS_CONFIG_DIR when already set (e.g. via .mcp.json).
    os.environ.setdefault("MAERIS_CONFIG_DIR", str(_repo_config_dir()))


# ---------------------------------------------------------------------------
# Auth group
# ---------------------------------------------------------------------------


@cli.group("auth", cls=MaerisGroup)
def auth_group() -> None:
    """Authenticate and manage your identity."""
    pass


# ---------------------------------------------------------------------------
# App group
# ---------------------------------------------------------------------------


@cli.group("app", cls=MaerisGroup)
def app_group() -> None:
    """Manage applications, linked repos, and MCP setup."""
    pass


# ---------------------------------------------------------------------------
# Test group
# ---------------------------------------------------------------------------


@cli.group("test", cls=MaerisGroup)
def test_group() -> None:
    """Generate, run, import, fix, and manage test cases."""
    pass


# ---------------------------------------------------------------------------
# Folder group
# ---------------------------------------------------------------------------


@cli.group("folder", cls=MaerisGroup)
def folder_group() -> None:
    """Manage testcase folders (groups)."""
    pass


# ---------------------------------------------------------------------------
# API group + param sub-group
# ---------------------------------------------------------------------------


@cli.group("api", cls=MaerisGroup)
def api_group() -> None:
    """Manage API definitions — create, list, update, run flows, and more."""
    pass


@api_group.group("param", cls=MaerisGroup)
def api_param_group() -> None:
    """Manage API parameters and headers."""
    pass


# ---------------------------------------------------------------------------
# Collection group
# ---------------------------------------------------------------------------


@cli.group("collection", cls=MaerisGroup)
def collection_group() -> None:
    """Manage API collections."""
    pass


# ---------------------------------------------------------------------------
# Env group + data sub-group
# ---------------------------------------------------------------------------


@cli.group("env", cls=MaerisGroup)
def env_group() -> None:
    """Manage environments."""
    pass


@env_group.group("data", cls=MaerisGroup)
def env_data_group() -> None:
    """Manage environment key/value fields."""
    pass


# ---------------------------------------------------------------------------
# Flow group + map and param sub-groups
# ---------------------------------------------------------------------------


@cli.group("flow", cls=MaerisGroup)
def flow_group() -> None:
    """Manage and run API flows."""
    pass


@flow_group.group("map", cls=MaerisGroup)
def flow_map_group() -> None:
    """Manage the flow map (step sequence) for a flow."""
    pass


@flow_group.group("param", cls=MaerisGroup)
def flow_param_group() -> None:
    """Manage flow parameters."""
    pass


# ---------------------------------------------------------------------------
# Assertion group
# ---------------------------------------------------------------------------


@cli.group("assertion", cls=MaerisGroup)
def assertion_group() -> None:
    """Manage API response assertions."""
    pass


# ---------------------------------------------------------------------------
# Coverage group  (name kept — coverage_cmds.py imports 'coverage')
# ---------------------------------------------------------------------------


@cli.group("coverage", cls=MaerisGroup)
def coverage() -> None:
    """Show test coverage for your UI or API."""
    pass


# ---------------------------------------------------------------------------
# Scan group  (name kept — scan_cmds.py imports 'scan_group')
# ---------------------------------------------------------------------------


@cli.group("scan", cls=MaerisGroup)
def scan_group() -> None:
    """Scan your codebase for security issues or extract API definitions."""
    pass


@cli.group("push", cls=MaerisGroup)
def push_group() -> None:
    """Push tests or scan results to the Maeris portal."""
    pass


# ---------------------------------------------------------------------------
# Report group
# ---------------------------------------------------------------------------


@cli.group("report", cls=MaerisGroup)
def report_group() -> None:
    """Generate exportable reports for tests, coverage, and security."""
    pass


# ---------------------------------------------------------------------------
# Config group
# ---------------------------------------------------------------------------


@cli.group("config", cls=MaerisGroup)
def config_group() -> None:
    """Manage persistent CLI configuration and shell completions."""
    pass


# ---------------------------------------------------------------------------
# UI group
# ---------------------------------------------------------------------------


@cli.group("ui", cls=MaerisGroup)
def ui_group() -> None:
    """UI instrumentation utilities."""
    pass


# ---------------------------------------------------------------------------
# Repair group
# ---------------------------------------------------------------------------


@cli.group("repair", cls=MaerisGroup)
def repair_group() -> None:
    """Detect and repair drift between your codebase and Maeris."""
    pass


# ---------------------------------------------------------------------------
# Baseline group
# ---------------------------------------------------------------------------


@cli.group("baseline", cls=MaerisGroup)
def baseline_group() -> None:
    """Generate a stable baseline test suite from your frontend source."""
    pass
