"""Step definitions for cloud_resources.feature."""

from __future__ import annotations

import os
from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when  # type: ignore[attr-defined]

from cleveragents.application.services._resource_registry_data import (
    BUILTIN_TYPES as _BUILTIN_TYPES,
)
from cleveragents.domain.models.core.resource import (
    PhysVirt,
    Resource,
    ResourceCapabilities,
)
from cleveragents.resource.handlers.cloud import (
    CloudResourceHandler,
    CloudSandboxStrategy,
    resolve_credentials,
    validate_credentials,
)
from cleveragents.resource.handlers.protocol import ResourceHandler

# ── Environment variable management ─────────────────────────

_CLOUD_ENV_VARS = [
    "AWS_ACCESS_KEY_ID",
    "AWS_SECRET_ACCESS_KEY",
    "AWS_SESSION_TOKEN",
    "AWS_REGION",
    "AWS_PROFILE",
    "GOOGLE_APPLICATION_CREDENTIALS",
    "GCLOUD_PROJECT",
    "GCP_REGION",
    "AZURE_SUBSCRIPTION_ID",
    "AZURE_TENANT_ID",
    "AZURE_CLIENT_ID",
    "AZURE_CLIENT_SECRET",
    "AZURE_REGION",
]


def _clear_cloud_env() -> dict[str, str]:
    """Remove all cloud env vars and return saved values."""
    saved: dict[str, str] = {}
    for var in _CLOUD_ENV_VARS:
        val = os.environ.pop(var, None)
        if val is not None:
            saved[var] = val
    return saved


def _restore_cloud_env(saved: dict[str, str]) -> None:
    """Restore previously saved env vars."""
    for var in _CLOUD_ENV_VARS:
        if var in saved:
            os.environ[var] = saved[var]
        else:
            os.environ.pop(var, None)


def _make_resource(  # type: ignore[attr-defined]
    provider: str,
    properties: dict[str, Any] | None = None,
) -> Resource:
    """Create a test cloud resource."""
    return Resource(
        resource_id="01KJ5C5TPMP8GGX3QC83E2MAQS",
        resource_type_name=provider,
        classification=PhysVirt.PHYSICAL,
        location=None,
        properties=properties or {},
        capabilities=ResourceCapabilities(
            readable=True,
            writable=True,
            sandboxable=False,
            checkpointable=False,
        ),
    )


# ── Given steps ──────────────────────────────────────────────


@given('a cloud resource type definition for "{provider}"')
def step_cloud_type_def(context: Any, provider: str) -> None:
    """Load the built-in cloud type definition."""
    for builtin in _BUILTIN_TYPES:
        if builtin["name"] == provider:
            context.cloud_type_def = builtin  # type: ignore[attr-defined]
            return
    raise AssertionError(f"Built-in type '{provider}' not found in _BUILTIN_TYPES")


@given("AWS environment variables are set")
def step_aws_env_set(context: Any) -> None:
    """Set AWS environment variables for testing."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["AWS_ACCESS_KEY_ID"] = "AKIAIOSFODNN7EXAMPLE"
    os.environ["AWS_SECRET_ACCESS_KEY"] = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    os.environ["AWS_REGION"] = "us-east-1"


@given("GCP environment variables are set")
def step_gcp_env_set(context: Any) -> None:
    """Set GCP environment variables for testing."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/sa-key.json"
    os.environ["GCLOUD_PROJECT"] = "my-gcp-project"


@given("Azure environment variables are set")
def step_azure_env_set(context: Any) -> None:
    """Set Azure environment variables for testing."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["AZURE_SUBSCRIPTION_ID"] = "sub-12345"
    os.environ["AZURE_TENANT_ID"] = "tenant-67890"
    os.environ["AZURE_CLIENT_ID"] = "client-abcde"
    os.environ["AZURE_CLIENT_SECRET"] = "secret-fghij"


@given("no cloud environment variables are set")
def step_no_cloud_env(context: Any) -> None:
    """Clear all cloud environment variables."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]


@given('AWS profile environment variable is set to "{profile}"')
def step_aws_profile_set(context: Any, profile: str) -> None:
    """Set only the AWS_PROFILE env var."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["AWS_PROFILE"] = profile


@given("a valid AWS cloud resource")
def step_valid_aws_resource(context: Any) -> None:
    """Create an AWS resource with credentials from env vars."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["AWS_ACCESS_KEY_ID"] = "AKIAIOSFODNN7EXAMPLE"
    os.environ["AWS_SECRET_ACCESS_KEY"] = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    context.cloud_resource = _make_resource("aws")  # type: ignore[attr-defined]


@given("a valid GCP cloud resource")
def step_valid_gcp_resource(context: Any) -> None:
    """Create a GCP resource with credentials from env vars."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/sa-key.json"
    os.environ["GCLOUD_PROJECT"] = "my-gcp-project"
    context.cloud_resource = _make_resource("gcp")  # type: ignore[attr-defined]


@given("a valid Azure cloud resource")
def step_valid_azure_resource(context: Any) -> None:
    """Create an Azure resource with credentials from env vars."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    os.environ["AZURE_SUBSCRIPTION_ID"] = "sub-12345"
    os.environ["AZURE_TENANT_ID"] = "tenant-67890"
    os.environ["AZURE_CLIENT_ID"] = "client-abcde"
    os.environ["AZURE_CLIENT_SECRET"] = "secret-fghij"
    context.cloud_resource = _make_resource("azure")  # type: ignore[attr-defined]


@given("an AWS cloud resource without credentials")
def step_aws_no_creds(context: Any) -> None:
    """Create an AWS resource with no credentials."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    context.cloud_resource = _make_resource("aws")  # type: ignore[attr-defined]


@given("a GCP cloud resource without credentials")
def step_gcp_no_creds(context: Any) -> None:
    """Create a GCP resource with no credentials."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    context.cloud_resource = _make_resource("gcp")  # type: ignore[attr-defined]


@given("an Azure cloud resource without credentials")
def step_azure_no_creds(context: Any) -> None:
    """Create an Azure resource with no credentials."""
    context.saved_env = _clear_cloud_env()  # type: ignore[attr-defined]
    context.cloud_resource = _make_resource("azure")  # type: ignore[attr-defined]


@given('a cloud sandbox strategy for "{provider}"')
def step_cloud_sandbox_strategy(context: Any, provider: str) -> None:
    """Create a cloud sandbox strategy instance."""
    context.cloud_sandbox = CloudSandboxStrategy(provider)  # type: ignore[attr-defined]


@given("a CloudResourceHandler instance")
def step_cloud_handler_instance(context: Any) -> None:
    """Create a CloudResourceHandler instance."""
    context.cloud_handler = CloudResourceHandler()  # type: ignore[attr-defined]


# ── When steps ───────────────────────────────────────────────


@when('I resolve credentials for provider "{provider}"')
def step_resolve_creds(context: Any, provider: str) -> None:
    """Resolve credentials for the given provider."""
    try:
        context.resolved_creds = resolve_credentials(provider, {})  # type: ignore[attr-defined]
        context.resolve_error = None  # type: ignore[attr-defined]
    except (ValueError, KeyError) as exc:
        context.resolved_creds = {}  # type: ignore[attr-defined]
        context.resolve_error = exc  # type: ignore[attr-defined]
    finally:
        saved = getattr(context, "saved_env", {})
        _restore_cloud_env(saved)


@when('I validate credentials for provider "{provider}"')
def step_validate_creds(context: Any, provider: str) -> None:
    """Validate resolved credentials."""
    resolved: dict[str, str | None] = getattr(context, "resolved_creds", {})
    context.validation_errors = validate_credentials(provider, resolved)  # type: ignore[attr-defined]


@when("I call resolve on the cloud handler")
def step_call_resolve(context: Any) -> None:
    """Call resolve on CloudResourceHandler."""
    handler = CloudResourceHandler()
    resource: Resource = context.cloud_resource  # type: ignore[attr-defined]
    mock_manager = MagicMock()
    try:
        handler.resolve(
            resource=resource,
            plan_id="PLAN_TEST",
            slot_name="cloud-slot",
            sandbox_manager=mock_manager,
        )
        context.handler_error = None  # type: ignore[attr-defined]
        context.handler_error_type = None  # type: ignore[attr-defined]
    except NotImplementedError as exc:
        context.handler_error = exc  # type: ignore[attr-defined]
        context.handler_error_type = "NotImplementedError"  # type: ignore[attr-defined]
    except ValueError as exc:
        context.handler_error = exc  # type: ignore[attr-defined]
        context.handler_error_type = "ValueError"  # type: ignore[attr-defined]
    finally:
        saved = getattr(context, "saved_env", {})
        _restore_cloud_env(saved)


@when('I try to resolve credentials for provider "{provider}"')
def step_try_resolve_unknown(context: Any, provider: str) -> None:
    """Try to resolve credentials for an unknown provider."""
    try:
        resolve_credentials(provider, {})
        context.handler_error = None  # type: ignore[attr-defined]
        context.handler_error_type = None  # type: ignore[attr-defined]
    except ValueError as exc:
        context.handler_error = exc  # type: ignore[attr-defined]
        context.handler_error_type = "ValueError"  # type: ignore[attr-defined]


@when("I call create on the sandbox strategy")
def step_sandbox_create(context: Any) -> None:
    """Call create on the cloud sandbox strategy."""
    strategy: CloudSandboxStrategy = context.cloud_sandbox  # type: ignore[attr-defined]
    try:
        strategy.create("res-test", "plan-test")
        context.handler_error = None  # type: ignore[attr-defined]
        context.handler_error_type = None  # type: ignore[attr-defined]
    except NotImplementedError as exc:
        context.handler_error = exc  # type: ignore[attr-defined]
        context.handler_error_type = "NotImplementedError"  # type: ignore[attr-defined]


@when("I call commit on the sandbox strategy")
def step_sandbox_commit(context: Any) -> None:
    """Call commit on the cloud sandbox strategy."""
    strategy: CloudSandboxStrategy = context.cloud_sandbox  # type: ignore[attr-defined]
    try:
        strategy.commit("res-test", "plan-test")
        context.handler_error = None  # type: ignore[attr-defined]
        context.handler_error_type = None  # type: ignore[attr-defined]
    except NotImplementedError as exc:
        context.handler_error = exc  # type: ignore[attr-defined]
        context.handler_error_type = "NotImplementedError"  # type: ignore[attr-defined]


@when("I call rollback on the sandbox strategy")
def step_sandbox_rollback(context: Any) -> None:
    """Call rollback on the cloud sandbox strategy."""
    strategy: CloudSandboxStrategy = context.cloud_sandbox  # type: ignore[attr-defined]
    try:
        strategy.rollback("res-test", "plan-test")
        context.handler_error = None  # type: ignore[attr-defined]
        context.handler_error_type = None  # type: ignore[attr-defined]
    except NotImplementedError as exc:
        context.handler_error = exc  # type: ignore[attr-defined]
        context.handler_error_type = "NotImplementedError"  # type: ignore[attr-defined]


@when("I validate the cloud sandbox strategy")
def step_validate_sandbox(context: Any) -> None:
    """Validate the cloud sandbox strategy."""
    strategy: CloudSandboxStrategy = context.cloud_sandbox  # type: ignore[attr-defined]
    context.validation_errors = strategy.validate({})  # type: ignore[attr-defined]
    saved = getattr(context, "saved_env", {})
    _restore_cloud_env(saved)


# ── Then steps ───────────────────────────────────────────────


@then('the cloud type name should be "{name}"')
def step_cloud_type_name(context: Any, name: str) -> None:
    """Check cloud type name."""
    assert context.cloud_type_def["name"] == name  # type: ignore[attr-defined]


@then('the cloud type sandbox_strategy should be "{strategy}"')
def step_cloud_type_strategy(context: Any, strategy: str) -> None:
    """Check cloud type sandbox strategy."""
    assert context.cloud_type_def["sandbox_strategy"] == strategy  # type: ignore[attr-defined]


@then('the cloud type resource_kind should be "{kind}"')
def step_cloud_type_kind(context: Any, kind: str) -> None:
    """Check cloud type resource kind."""
    assert context.cloud_type_def["resource_kind"] == kind  # type: ignore[attr-defined]


@then("the cloud type should be built_in")
def step_cloud_type_builtin(context: Any) -> None:
    """Check cloud type is built-in."""
    assert context.cloud_type_def["built_in"] is True  # type: ignore[attr-defined]


@then("the cloud type should not be user_addable")
def step_cloud_type_not_user_addable(context: Any) -> None:
    """Check cloud type is not user-addable."""
    assert context.cloud_type_def["user_addable"] is False  # type: ignore[attr-defined]


@then("the cloud type should have {count:d} cli_args")
def step_cloud_type_cli_args_count(context: Any, count: int) -> None:
    """Check cloud type CLI args count."""
    assert len(context.cloud_type_def["cli_args"]) == count  # type: ignore[attr-defined]


@then('the cloud type cli_args should include "{arg_name}"')
def step_cloud_type_cli_arg(context: Any, arg_name: str) -> None:
    """Check cloud type has a specific CLI arg."""
    names = [a["name"] for a in context.cloud_type_def["cli_args"]]  # type: ignore[attr-defined]
    assert arg_name in names, f"'{arg_name}' not in {names}"


@then('the resolved credential "{field}" should not be None')
def step_cred_not_none(context: Any, field: str) -> None:
    """Check a resolved credential is not None."""
    assert context.resolved_creds.get(field) is not None, (  # type: ignore[attr-defined]
        f"Credential '{field}' is None"
    )


@then("there should be no cloud validation errors")
def step_no_cloud_validation_errors(context: Any) -> None:
    """Check there are no cloud validation errors."""
    errors: list[str] = getattr(context, "validation_errors", [])
    assert len(errors) == 0, f"Expected no errors, got: {errors}"


@then("there should be cloud validation errors")
def step_has_cloud_validation_errors(context: Any) -> None:
    """Check there are cloud validation errors."""
    errors: list[str] = getattr(context, "validation_errors", [])
    assert len(errors) > 0, "Expected validation errors but got none"


@then('the cloud validation errors should mention "{field}"')
def step_cloud_errors_mention(context: Any, field: str) -> None:
    """Check cloud validation errors mention a specific field."""
    errors: list[str] = getattr(context, "validation_errors", [])
    combined = " ".join(errors)
    assert field in combined, f"'{field}' not found in errors: {combined}"


@then("the cloud validation errors should not contain secrets")
def step_no_secrets_in_cloud_errors(context: Any) -> None:
    """Ensure no raw secret values appear in error messages."""
    errors: list[str] = getattr(context, "validation_errors", [])
    combined = " ".join(errors)
    # Secrets should never appear in error text
    assert "AKIAIOSFODNN7EXAMPLE" not in combined
    assert "wJalrXUtnFEMI" not in combined


@then("a cloud NotImplementedError should be raised")
def step_cloud_not_implemented(context: Any) -> None:
    """Check that a NotImplementedError was raised."""
    assert context.handler_error_type == "NotImplementedError", (  # type: ignore[attr-defined]
        f"Expected NotImplementedError, got {context.handler_error_type}: "  # type: ignore[attr-defined]
        f"{context.handler_error}"  # type: ignore[attr-defined]
    )


@then("a cloud ValueError should be raised")
def step_cloud_value_error(context: Any) -> None:
    """Check that a ValueError was raised."""
    assert context.handler_error_type == "ValueError", (  # type: ignore[attr-defined]
        f"Expected ValueError, got {context.handler_error_type}: "  # type: ignore[attr-defined]
        f"{context.handler_error}"  # type: ignore[attr-defined]
    )


@then('the cloud error message should mention "{text}"')
def step_cloud_error_mentions(context: Any, text: str) -> None:
    """Check cloud error message contains expected text."""
    msg = str(context.handler_error)  # type: ignore[attr-defined]
    assert text in msg, f"'{text}' not found in error: {msg}"


@then('the cloud type should inherit from "{parent}"')
def step_cloud_type_inherits(context: Any, parent: str) -> None:
    """Check cloud type inherits from expected parent."""
    actual = context.cloud_type_def.get("inherits")  # type: ignore[attr-defined]
    assert actual == parent, f"Expected inherits='{parent}', got '{actual}'"


@then("the cloud handler should satisfy the ResourceHandler protocol")
def step_cloud_satisfies_protocol(context: Any) -> None:
    """Check CloudResourceHandler satisfies ResourceHandler protocol."""
    handler: CloudResourceHandler = context.cloud_handler  # type: ignore[attr-defined]
    assert isinstance(handler, ResourceHandler), (
        "CloudResourceHandler does not satisfy ResourceHandler protocol"
    )
