"""Step definitions for config_resolution.feature."""

from __future__ import annotations

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

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

from cleveragents.application.services.config_service import ConfigService

use_step_matcher("re")


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


def _get_service(context: Context) -> ConfigService:
    """Return the test-scoped ConfigService instance."""
    svc: ConfigService = context._config_service
    return svc


def _cleanup(context: Context) -> None:
    """Clean up temp dir and env vars."""
    tmpdir = getattr(context, "_config_tmpdir", None)
    if tmpdir and os.path.isdir(tmpdir):
        shutil.rmtree(tmpdir, ignore_errors=True)
    for var in getattr(context, "_env_vars_set", []):
        os.environ.pop(var, None)


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


@given("a clean config service test environment")
def step_clean_config_service_env(context: Context) -> None:
    tmpdir = tempfile.mkdtemp()
    context._config_tmpdir = tmpdir
    tmp_path = Path(tmpdir)
    context._config_service = ConfigService(
        config_dir=tmp_path,
        config_path=tmp_path / "config.toml",
    )
    context._env_vars_set = []
    if not hasattr(context, "_cleanup_handlers"):
        context._cleanup_handlers = []
    context._cleanup_handlers.append(lambda: _cleanup(context))


# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------


@given(
    r'I have written global config key "(?P<key>[^"]+)" with value "(?P<value>[^"]+)"'
)
def step_write_global_config(context: Context, key: str, value: str) -> None:
    svc = _get_service(context)
    data = svc.read_config()
    data[key] = value
    svc.write_config(data)


@given(
    r'I have written project "(?P<project>[^"]+)" config key "(?P<key>[^"]+)" with value "(?P<value>[^"]+)"'
)
def step_write_project_config(
    context: Context, project: str, key: str, value: str
) -> None:
    svc = _get_service(context)
    data = svc.read_config()
    if "project" not in data:
        data["project"] = {}
    proj_data: dict[str, Any] = data["project"]
    if project not in proj_data:
        proj_data[project] = {}
    proj_data[project][key] = value
    svc.write_config(data)


@given(r'I write config data with key "(?P<key>[^"]+)" and value "(?P<value>[^"]+)"')
def step_write_config_data(context: Context, key: str, value: str) -> None:
    svc = _get_service(context)
    svc.set_value(key, value)


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


@when(r'I resolve config key "(?P<key>[^"]+)" with CLI value "(?P<cli_value>[^"]+)"')
def step_resolve_key_cli(context: Context, key: str, cli_value: str) -> None:
    svc = _get_service(context)
    context.resolved = svc.resolve(key, cli_value=cli_value)


@when(r'I resolve config key "(?P<key>[^"]+)" for project "(?P<project>[^"]+)"')
def step_resolve_key_project(context: Context, key: str, project: str) -> None:
    svc = _get_service(context)
    context.resolved = svc.resolve(key, project_name=project)


@when(r'I resolve config key "(?P<key>[^"]+)" with verbose mode')
def step_resolve_key_verbose(context: Context, key: str) -> None:
    svc = _get_service(context)
    context.resolved = svc.resolve(key, verbose=True)


@when(r'I resolve config key "(?P<key>[^"]+)"')
def step_resolve_key(context: Context, key: str) -> None:
    svc = _get_service(context)
    context.resolved = svc.resolve(key)


@when(r'I look up the env var for key "(?P<key>[^"]+)"')
def step_lookup_env_var(context: Context, key: str) -> None:
    svc = _get_service(context)
    context.env_var_name = svc.env_var_for_key(key)


@when(r'I attempt to resolve unknown key "(?P<key>[^"]+)"')
def step_attempt_resolve_unknown(context: Context, key: str) -> None:
    svc = _get_service(context)
    try:
        svc.resolve(key)
        context.raised_error = None
    except ValueError as exc:
        context.raised_error = exc


@when(r'I attempt to validate unknown key "(?P<key>[^"]+)"')
def step_attempt_validate_unknown(context: Context, key: str) -> None:
    try:
        ConfigService.validate_key(key)
        context.raised_error = None
    except ValueError as exc:
        context.raised_error = exc


@when(
    r'I attempt to validate type for key "(?P<key>[^"]+)" with value "(?P<value>[^"]+)"'
)
def step_attempt_validate_type(context: Context, key: str, value: str) -> None:
    try:
        ConfigService.validate_type(key, value)
        context.raised_error = None
    except (TypeError, ValueError) as exc:
        context.raised_error = exc


@when("I read the config file")
def step_read_config_file(context: Context) -> None:
    svc = _get_service(context)
    context.config_data = svc.read_config()


@when("I write config data to a new directory")
def step_write_to_new_dir(context: Context) -> None:
    base = Path(context._config_tmpdir) / "nested" / "subdir"
    context._new_config_dir = base
    svc = ConfigService(config_dir=base, config_path=base / "config.toml")
    svc.write_config({"test_key": "test_value"})


@when("I resolve all keys")
def step_resolve_all(context: Context) -> None:
    svc = _get_service(context)
    context.all_resolved = svc.resolve_all()


@when(r'I set config value "(?P<key>[^"]+)" to "(?P<value>[^"]+)"')
def step_set_config_value(context: Context, key: str, value: str) -> None:
    svc = _get_service(context)
    svc.set_value(key, value)


@when(r'I get entry for key "(?P<key>[^"]+)"')
def step_get_entry(context: Context, key: str) -> None:
    context.entry = ConfigService.get_entry(key)


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


@then(r'the resolved value should be "(?P<value>[^"]+)"')
def step_resolved_value(context: Context, value: str) -> None:
    assert str(context.resolved.value) == value, (
        f"Expected '{value}', got '{context.resolved.value}'"
    )


@then(r'the resolved source should be "(?P<source>[^"]+)"')
def step_resolved_source(context: Context, source: str) -> None:
    assert context.resolved.source.value == source, (
        f"Expected source '{source}', got '{context.resolved.source.value}'"
    )


@then(r'the env var name should be "(?P<expected>[^"]+)"')
def step_env_var_name(context: Context, expected: str) -> None:
    assert context.env_var_name == expected, (
        f"Expected '{expected}', got '{context.env_var_name}'"
    )


@then(r'the config ValueError message should contain "(?P<fragment>[^"]+)"')
def step_config_value_error_raised(context: Context, fragment: str) -> None:
    assert context.raised_error is not None, "Expected ValueError but none was raised"
    assert isinstance(context.raised_error, ValueError), (
        f"Expected ValueError, got {type(context.raised_error).__name__}"
    )
    assert fragment in str(context.raised_error), (
        f"Expected '{fragment}' in message: {context.raised_error}"
    )


@then(r'the config TypeError message should contain "(?P<fragment>[^"]+)"')
def step_config_type_error_raised(context: Context, fragment: str) -> None:
    assert context.raised_error is not None, "Expected TypeError but none was raised"
    assert isinstance(context.raised_error, TypeError), (
        f"Expected TypeError, got {type(context.raised_error).__name__}"
    )
    assert fragment in str(context.raised_error), (
        f"Expected '{fragment}' in message: {context.raised_error}"
    )


@then(r"the resolved value should be integer (?P<value>\d+)")
def step_resolved_int(context: Context, value: str) -> None:
    expected = int(value)
    assert context.resolved.value == expected, (
        f"Expected {expected}, got {context.resolved.value}"
    )
    assert isinstance(context.resolved.value, int), (
        f"Expected int, got {type(context.resolved.value).__name__}"
    )


@then("the resolved value should be boolean true")
def step_resolved_bool_true(context: Context) -> None:
    assert context.resolved.value is True, (
        f"Expected True, got {context.resolved.value}"
    )


@then("the resolved value should be boolean false")
def step_resolved_bool_false(context: Context) -> None:
    assert context.resolved.value is False, (
        f"Expected False, got {context.resolved.value}"
    )


@then(r"the resolved value should be float (?P<value>[\d.]+)")
def step_resolved_float(context: Context, value: str) -> None:
    expected = float(value)
    assert abs(context.resolved.value - expected) < 0.001, (
        f"Expected {expected}, got {context.resolved.value}"
    )


@then(r"the resolution chain should have (?P<count>\d+) entries")
def step_chain_length(context: Context, count: str) -> None:
    expected = int(count)
    assert len(context.resolved.chain) == expected, (
        f"Expected {expected} entries, got {len(context.resolved.chain)}"
    )


@then(r'the chain should include source "(?P<source>[^"]+)"')
def step_chain_includes_source(context: Context, source: str) -> None:
    sources = [entry["source"] for entry in context.resolved.chain]
    assert source in sources, f"Expected source '{source}' in chain: {sources}"


@then(r'the registry should contain key "(?P<key>[^"]+)"')
def step_registry_contains(context: Context, key: str) -> None:
    registry = ConfigService.registry()
    assert key in registry, f"Key '{key}' not found in registry"


@then(
    r'the config data should contain key "(?P<key>[^"]+)" with value "(?P<value>[^"]+)"'
)
def step_config_data_contains(context: Context, key: str, value: str) -> None:
    data = context.config_data
    assert key in data, f"Key '{key}' not in config data: {data}"
    assert str(data[key]) == value, (
        f"Expected '{value}' for key '{key}', got '{data[key]}'"
    )


@then("the config directory should exist")
def step_config_dir_exists(context: Context) -> None:
    assert context._new_config_dir.exists(), (
        f"Config dir {context._new_config_dir} does not exist"
    )


@then("the result should contain all registered keys")
def step_result_has_all_keys(context: Context) -> None:
    registered = ConfigService.registered_keys()
    for key in registered:
        assert key in context.all_resolved, (
            f"Key '{key}' missing from resolve_all result"
        )


@then(r'the entry should have section "(?P<section>[^"]+)"')
def step_entry_section(context: Context, section: str) -> None:
    assert context.entry is not None, "Entry should not be None"
    assert context.entry.section == section, (
        f"Expected section '{section}', got '{context.entry.section}'"
    )


@then(r'the entry should have python type "(?P<type_name>[^"]+)"')
def step_entry_type(context: Context, type_name: str) -> None:
    assert context.entry is not None, "Entry should not be None"
    assert context.entry.python_type.__name__ == type_name, (
        f"Expected type '{type_name}', got '{context.entry.python_type.__name__}'"
    )


@then("the entry should be None")
def step_entry_is_none(context: Context) -> None:
    assert context.entry is None, f"Expected None, got {context.entry}"


# Restore default step matcher for other step files
use_step_matcher("parse")
