"""Step definitions for config_three_scope.feature — three-scope resolution."""

from __future__ import annotations

import os
import shutil
import tempfile
import tomllib
from pathlib import Path
from typing import Any

import tomlkit
from behave import given, then, when
from behave.runner import Context

from cleveragents.application.services import config_service as _config_service_mod
from cleveragents.application.services.config_service import (
    ConfigLevel,
    ConfigScope,
    ConfigService,
    discover_project_root,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _svc(context: Context) -> ConfigService:
    return context._three_scope_service


def _write_toml(path: Path, data: dict[str, Any]) -> None:
    """Write *data* as TOML to *path*, creating parent dirs."""
    path.parent.mkdir(parents=True, exist_ok=True)
    doc = tomlkit.document()
    for key, value in data.items():
        doc[key] = value
    with open(path, "w") as fh:
        tomlkit.dump(doc, fh)


def _read_toml(path: Path) -> dict[str, Any]:
    if not path.exists():
        return {}
    with open(path, "rb") as fh:
        return tomllib.load(fh)


def _cleanup(context: Context) -> None:
    tmpdir = getattr(context, "_three_scope_tmpdir", None)
    if tmpdir and os.path.isdir(tmpdir):
        shutil.rmtree(tmpdir, ignore_errors=True)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("a clean three-scope config environment")
def step_clean_three_scope_env(context: Context) -> None:
    tmpdir = tempfile.mkdtemp()
    context._three_scope_tmpdir = tmpdir
    base = Path(tmpdir)

    # Global config dir
    global_dir = base / "global_config"
    global_dir.mkdir()

    # Project root (with marker)
    project_root = base / "project"
    project_root.mkdir()
    (project_root / "cleveragents.toml").touch()

    context._three_scope_global_dir = global_dir
    context._three_scope_project_root = project_root

    context._three_scope_service = ConfigService(
        config_dir=global_dir,
        config_path=global_dir / "config.toml",
        project_root=project_root,
    )

    if not hasattr(context, "_cleanup_handlers"):
        context._cleanup_handlers = []
    context._cleanup_handlers.append(lambda: _cleanup(context))


# ---------------------------------------------------------------------------
# Given — scope values
# ---------------------------------------------------------------------------


@given('a global config value for three-scope "{key}" = "{value}"')
def step_global_value(context: Context, key: str, value: str) -> None:
    svc = _svc(context)
    data = svc.read_config()
    data[key] = value
    svc.write_config(data)


@given('a project config value "{key}" = "{value}"')
def step_project_value(context: Context, key: str, value: str) -> None:
    path = context._three_scope_project_root / "config.toml"
    data = _read_toml(path)
    data[key] = value
    _write_toml(path, data)


@given('a local config value "{key}" = "{value}"')
def step_local_value(context: Context, key: str, value: str) -> None:
    path = context._three_scope_project_root / "config.local.toml"
    data = _read_toml(path)
    data[key] = value
    _write_toml(path, data)


# Given — nested data for deep merge
@given('a global config with nested data "{dotted}" = "{value}"')
def step_global_nested(context: Context, dotted: str, value: str) -> None:
    svc = _svc(context)
    data = svc.read_config()
    section, key = dotted.split(".", 1)
    data.setdefault(section, {})[key] = value
    svc.write_config(data)


@given('a project config with nested data "{dotted}" = "{value}"')
def step_project_nested(context: Context, dotted: str, value: str) -> None:
    path = context._three_scope_project_root / "config.toml"
    data = _read_toml(path)
    section, key = dotted.split(".", 1)
    data.setdefault(section, {})[key] = value
    _write_toml(path, data)


@given('a local config with nested data "{dotted}" = "{value}"')
def step_local_nested(context: Context, dotted: str, value: str) -> None:
    path = context._three_scope_project_root / "config.local.toml"
    data = _read_toml(path)
    section, key = dotted.split(".", 1)
    data.setdefault(section, {})[key] = value
    _write_toml(path, data)


# Given — project root discovery
@given('a directory tree with "{marker}" marker at the root')
def step_dir_tree_with_marker_file(context: Context, marker: str) -> None:
    tmpdir = tempfile.mkdtemp()
    context._discovery_tmpdir = tmpdir
    root = Path(tmpdir) / "project_root"
    root.mkdir()
    sub = root / "a" / "b"
    sub.mkdir(parents=True)
    target = root / marker
    if marker.startswith("."):
        target.mkdir(parents=True, exist_ok=True)
    else:
        target.touch()
    context._discovery_root = root
    context._discovery_sub = sub


@given('a directory tree with ".cleveragents" directory marker at the root')
def step_dir_tree_with_dir_marker(context: Context) -> None:
    tmpdir = tempfile.mkdtemp()
    context._discovery_tmpdir = tmpdir
    root = Path(tmpdir) / "project_root"
    root.mkdir()
    sub = root / "a" / "b"
    sub.mkdir(parents=True)
    (root / ".cleveragents").mkdir()
    context._discovery_root = root
    context._discovery_sub = sub


@given("a directory tree with no project markers")
def step_dir_tree_no_markers(context: Context) -> None:
    tmpdir = tempfile.mkdtemp()
    context._discovery_tmpdir = tmpdir
    context._discovery_sub = Path(tmpdir)
    # Temporarily replace markers with a name that cannot exist in the
    # ancestor chain so the walk-up never matches a real directory such
    # as /tmp/.cleveragents left by a prior CleverAgents installation.
    context._original_markers = _config_service_mod._PROJECT_ROOT_MARKERS
    _config_service_mod._PROJECT_ROOT_MARKERS = ("__cleveragents_nonexistent_marker__",)
    context._cleanup_handlers.append(
        lambda: setattr(
            _config_service_mod,
            "_PROJECT_ROOT_MARKERS",
            context._original_markers,
        )
    )


# Given — local config file
@given('a local config file with "{key}" = "{value}"')
def step_local_file_with_key(context: Context, key: str, value: str) -> None:
    path = context._three_scope_project_root / "config.local.toml"
    data = _read_toml(path)
    data[key] = value
    _write_toml(path, data)


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when('I resolve three-scope key "{key}"')
def step_resolve_three_scope(context: Context, key: str) -> None:
    svc = _svc(context)
    context._three_scope_resolved = svc.resolve(key)


@when("I read the three-scope merged config")
def step_read_merged(context: Context) -> None:
    svc = _svc(context)
    context._three_scope_merged = svc.read_merged_config()


@when("I discover the project root from a subdirectory")
def step_discover_from_subdir(context: Context) -> None:
    context._discovered_root = discover_project_root(context._discovery_sub)


@when("I discover the project root")
def step_discover_project_root(context: Context) -> None:
    context._discovered_root = discover_project_root(context._discovery_sub)


@when("I read the local config")
def step_read_local(context: Context) -> None:
    svc = _svc(context)
    context._local_config = svc.read_local_config()


@when('I set three-scope value "{key}" to "{value}" with scope "{scope}"')
def step_set_scoped(context: Context, key: str, value: str, scope: str) -> None:
    svc = _svc(context)
    config_scope = ConfigScope(scope)
    svc.set_value(key, value, scope=config_scope)


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then('the three-scope resolved value should be "{value}"')
def step_check_value(context: Context, value: str) -> None:
    actual = str(context._three_scope_resolved.value)
    assert actual == value, f"Expected '{value}', got '{actual}'"


@then('the three-scope resolved source should be "{source}"')
def step_check_source(context: Context, source: str) -> None:
    actual = context._three_scope_resolved.source.value
    assert actual == source, f"Expected source '{source}', got '{actual}'"


@then('the merged config "{section}" should contain key "{key}" with value "{value}"')
def step_merged_section_key(
    context: Context, section: str, key: str, value: str
) -> None:
    merged = context._three_scope_merged
    assert section in merged, f"Section '{section}' not in merged config: {merged}"
    sec = merged[section]
    assert key in sec, f"Key '{key}' not in section '{section}': {sec}"
    assert str(sec[key]) == value, (
        f"Expected '{value}' for {section}.{key}, got '{sec[key]}'"
    )


@then("the discovered project root should match the marker directory")
def step_root_matches(context: Context) -> None:
    expected = context._discovery_root.resolve()
    actual = context._discovered_root
    assert actual is not None, "Expected a project root, got None"
    assert actual.resolve() == expected, f"Expected {expected}, got {actual.resolve()}"


@then("the discovered project root should be None")
def step_root_none(context: Context) -> None:
    assert context._discovered_root is None, (
        f"Expected None, got {context._discovered_root}"
    )


@then("the local config should be an empty dict")
def step_local_empty(context: Context) -> None:
    assert context._local_config == {}, (
        f"Expected empty dict, got {context._local_config}"
    )


@then('the local config file should contain "{key}" = "{value}"')
def step_local_file_contains(context: Context, key: str, value: str) -> None:
    path = context._three_scope_project_root / "config.local.toml"
    data = _read_toml(path)
    assert key in data, f"Key '{key}' not in local config: {data}"
    assert str(data[key]) == value, f"Expected '{value}', got '{data[key]}'"


@then('the project config file should contain "{key}" = "{value}"')
def step_project_file_contains(context: Context, key: str, value: str) -> None:
    path = context._three_scope_project_root / "config.toml"
    data = _read_toml(path)
    assert key in data, f"Key '{key}' not in project config: {data}"
    assert str(data[key]) == value, f"Expected '{value}', got '{data[key]}'"


@then('the global config file should contain "{key}" = "{value}"')
def step_global_file_contains(context: Context, key: str, value: str) -> None:
    svc = _svc(context)
    data = svc.read_config()
    assert key in data, f"Key '{key}' not in global config: {data}"
    assert str(data[key]) == value, f"Expected '{value}', got '{data[key]}'"


@then('ConfigScope should have value "{value}"')
def step_scope_enum_value(context: Context, value: str) -> None:
    scope = ConfigScope(value)
    assert scope.value == value, f"Expected ConfigScope.{value}, got {scope}"


@then('ConfigLevel should have value "{value}"')
def step_level_enum_value(context: Context, value: str) -> None:
    level = ConfigLevel(value)
    assert level.value == value, f"Expected ConfigLevel.{value}, got {level}"


@then('ConfigLevel "{value}" should exist between "{before}" and "{after}"')
def step_level_ordering(context: Context, value: str, before: str, after: str) -> None:
    members = [m.value for m in ConfigLevel]
    idx_before = members.index(before)
    idx_value = members.index(value)
    idx_after = members.index(after)
    assert idx_before < idx_value < idx_after, (
        f"Expected {before} < {value} < {after} in {members}"
    )


@then('the default ignore patterns should include "{pattern}"')
def step_ignore_patterns(context: Context, pattern: str) -> None:
    from cleveragents.application.services.context_service import (
        DEFAULT_IGNORE_PATTERNS,
    )

    assert pattern in DEFAULT_IGNORE_PATTERNS, (
        f"'{pattern}' not in DEFAULT_IGNORE_PATTERNS: {DEFAULT_IGNORE_PATTERNS}"
    )
