"""Step definitions for ProjectContextPolicy domain model tests."""

from __future__ import annotations

from typing import Any

from behave import given, then, when
from behave.runner import Context
from pydantic import ValidationError

from cleveragents.domain.models.core.context_policy import (
    ContextView,
    ProjectContextPolicy,
)

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


def _default_view(**overrides: Any) -> ContextView:
    """Create a ContextView with sensible defaults."""
    defaults: dict[str, Any] = {}
    defaults.update(overrides)
    return ContextView(**defaults)


# -------------------------------------------------------------------
# Empty policy defaults
# -------------------------------------------------------------------


@when("I create an empty project context policy")
def step_create_empty_policy(context: Context) -> None:
    context.policy = ProjectContextPolicy()
    context.resolved_view = context.policy.default_view


@then("the default view should include all resources")
def step_default_includes_all_resources(context: Context) -> None:
    assert context.resolved_view.include_resources == []


@then("the default view should include all paths")
def step_default_includes_all_paths(context: Context) -> None:
    assert context.resolved_view.include_paths == []


@then("the default view should have no file size limit")
def step_default_no_file_size_limit(context: Context) -> None:
    assert context.resolved_view.max_file_size is None


@then("the default view should have no total size limit")
def step_default_no_total_size_limit(context: Context) -> None:
    assert context.resolved_view.max_total_size is None


# -------------------------------------------------------------------
# View inheritance — Given steps
# -------------------------------------------------------------------


@given("a policy with only a default view")
def step_policy_default_only(context: Context) -> None:
    context.default_view = ContextView(
        include_resources=["db-*"],
        max_file_size=1024,
    )
    context.policy = ProjectContextPolicy(
        default_view=context.default_view,
    )


@given("a policy with default and strategize views")
def step_policy_default_and_strategize(context: Context) -> None:
    context.default_view = ContextView(
        include_resources=["db-*"],
    )
    context.strategize_view = ContextView(
        include_resources=["db-*", "cache-*"],
    )
    context.policy = ProjectContextPolicy(
        default_view=context.default_view,
        strategize_view=context.strategize_view,
    )


@given("a policy with default strategize and execute views")
def step_policy_default_strat_exec(context: Context) -> None:
    context.default_view = ContextView(
        include_resources=["db-*"],
    )
    context.strategize_view = ContextView(
        include_resources=["db-*", "cache-*"],
    )
    context.execute_view = ContextView(
        include_resources=["db-*", "cache-*", "api-*"],
    )
    context.policy = ProjectContextPolicy(
        default_view=context.default_view,
        strategize_view=context.strategize_view,
        execute_view=context.execute_view,
    )


@given("a policy with a custom execute view")
def step_policy_custom_execute(context: Context) -> None:
    context.default_view = ContextView(
        include_resources=["db-*"],
    )
    context.execute_view = ContextView(
        include_resources=["all-*"],
        max_file_size=2048,
    )
    context.policy = ProjectContextPolicy(
        default_view=context.default_view,
        execute_view=context.execute_view,
    )


# -------------------------------------------------------------------
# View inheritance — When/Then steps
# -------------------------------------------------------------------


@when('I resolve the view for phase "{phase}"')
def step_resolve_view(context: Context, phase: str) -> None:
    context.resolved_view = context.policy.resolve_view(phase)


@then("the resolved view should be the default view")
def step_resolved_is_default(context: Context) -> None:
    assert context.resolved_view is context.default_view


@then("the resolved view should be the strategize view")
def step_resolved_is_strategize(context: Context) -> None:
    assert context.resolved_view is context.strategize_view


@then("the resolved view should be the execute view")
def step_resolved_is_execute(context: Context) -> None:
    assert context.resolved_view is context.execute_view


# -------------------------------------------------------------------
# Invalid phase
# -------------------------------------------------------------------


@when('I try to resolve the view for phase "{phase}"')
def step_try_resolve_invalid_phase(context: Context, phase: str) -> None:
    context.policy_error = None
    try:
        context.policy.resolve_view(phase)
    except ValueError as exc:
        context.policy_error = str(exc)


@then("a context policy error should be raised")
def step_policy_error_raised(context: Context) -> None:
    assert context.policy_error is not None, "Expected an error but none was raised"


@then('the context policy error should mention "{text}"')
def step_error_mentions(context: Context, text: str) -> None:
    assert text in context.policy_error, (
        f"Expected '{text}' in error: {context.policy_error}"
    )


# -------------------------------------------------------------------
# Include/exclude resources
# -------------------------------------------------------------------


@when('I create a context view with include resources "{patterns}"')
def step_create_view_include_resources(context: Context, patterns: str) -> None:
    names = [p.strip() for p in patterns.split(",")]
    context.ctx_view = ContextView(include_resources=names)


@then("the context view should have {count:d} include resources")
def step_view_include_resource_count(context: Context, count: int) -> None:
    assert len(context.ctx_view.include_resources) == count


@when('I create a context view with exclude resources "{patterns}"')
def step_create_view_exclude_resources(context: Context, patterns: str) -> None:
    names = [p.strip() for p in patterns.split(",")]
    context.ctx_view = ContextView(exclude_resources=names)


@then("the context view should have {count:d} exclude resource")
def step_view_exclude_resource_count_singular(context: Context, count: int) -> None:
    assert len(context.ctx_view.exclude_resources) == count


@when('I create a context view with include "{inc}" and exclude "{exc}"')
def step_create_view_include_exclude(context: Context, inc: str, exc: str) -> None:
    context.ctx_view = ContextView(
        include_resources=[inc],
        exclude_resources=[exc],
    )


@then("the context view should have {count:d} include resource")
def step_view_include_resource_count_singular(context: Context, count: int) -> None:
    assert len(context.ctx_view.include_resources) == count


# -------------------------------------------------------------------
# Include/exclude paths
# -------------------------------------------------------------------


@when('I create a context view with include paths "{globs}"')
def step_create_view_include_paths(context: Context, globs: str) -> None:
    paths = [g.strip() for g in globs.split(",")]
    context.ctx_view = ContextView(include_paths=paths)


@then("the context view should have {count:d} include paths")
def step_view_include_path_count(context: Context, count: int) -> None:
    assert len(context.ctx_view.include_paths) == count


@when('I create a context view with exclude paths "{globs}"')
def step_create_view_exclude_paths(context: Context, globs: str) -> None:
    paths = [g.strip() for g in globs.split(",")]
    context.ctx_view = ContextView(exclude_paths=paths)


@then("the context view should have {count:d} exclude paths")
def step_view_exclude_path_count(context: Context, count: int) -> None:
    assert len(context.ctx_view.exclude_paths) == count


# -------------------------------------------------------------------
# Size limit validation
# -------------------------------------------------------------------


@when("I create a context view with max file size {size:d}")
def step_create_view_max_file_size(context: Context, size: int) -> None:
    context.ctx_view = ContextView(max_file_size=size)


@then("the context view max file size should be {size:d}")
def step_view_max_file_size(context: Context, size: int) -> None:
    assert context.ctx_view.max_file_size == size


@when("I create a context view with no file size limit")
def step_create_view_no_file_limit(context: Context) -> None:
    context.ctx_view = ContextView()


@then("the context view max file size should be None")
def step_view_max_file_size_none(context: Context) -> None:
    assert context.ctx_view.max_file_size is None


@when("I try to create a context view with max file size {size:d}")
def step_try_create_view_bad_file_size(context: Context, size: int) -> None:
    context.policy_error = None
    try:
        ContextView(max_file_size=size)
    except ValidationError as exc:
        context.policy_error = str(exc)


@when("I create a context view with max total size {size:d}")
def step_create_view_max_total_size(context: Context, size: int) -> None:
    context.ctx_view = ContextView(max_total_size=size)


@then("the context view max total size should be {size:d}")
def step_view_max_total_size(context: Context, size: int) -> None:
    assert context.ctx_view.max_total_size == size


@when("I try to create a context view with max total size {size:d}")
def step_try_create_view_bad_total_size(context: Context, size: int) -> None:
    context.policy_error = None
    try:
        ContextView(max_total_size=size)
    except ValidationError as exc:
        context.policy_error = str(exc)


# -------------------------------------------------------------------
# Serialization round-trip
# -------------------------------------------------------------------


@given("a fully populated context policy")
def step_full_policy(context: Context) -> None:
    context.policy = ProjectContextPolicy(
        default_view=ContextView(
            include_resources=["db-*"],
            exclude_resources=["db-test"],
            include_paths=["src/**"],
            exclude_paths=["*.pyc"],
            max_file_size=1048576,
            max_total_size=10485760,
        ),
        strategize_view=ContextView(
            include_resources=["db-*", "cache-*"],
        ),
        execute_view=ContextView(
            include_paths=["src/**", "lib/**"],
        ),
        apply_view=ContextView(
            exclude_paths=["tests/**"],
        ),
    )


@when("I serialize and deserialize the policy")
def step_roundtrip(context: Context) -> None:
    json_str = context.policy.model_dump_json()
    context.roundtrip_policy = ProjectContextPolicy.model_validate_json(json_str)


@then("the deserialized policy should match the original")
def step_roundtrip_match(context: Context) -> None:
    assert context.roundtrip_policy == context.policy


# -------------------------------------------------------------------
# ContextView model_dump keys
# -------------------------------------------------------------------


@when("I create a context view with defaults")
def step_create_default_view(context: Context) -> None:
    context.ctx_view = ContextView()
    context.ctx_view_dump = context.ctx_view.model_dump()


@then('the context view dump should have key "{key}"')
def step_view_dump_has_key(context: Context, key: str) -> None:
    assert key in context.ctx_view_dump, (
        f"Key '{key}' not in dump: {list(context.ctx_view_dump.keys())}"
    )
