"""Helper for Workflow Example 8: Cloud Infrastructure Management.

Integration test exercising the ``supervised`` automation profile with:
- Custom resource type (local/terraform-state) registration (Step 1)
- Custom skill (local/terraform-ops) with tool composition (Step 1)
- Supervised automation profile behavior verification
- Action creation with invariants + plan use (Step 3)
- Infrastructure analysis with mocked Terraform operations
- Invariant enforcement verification

Spec reference: docs/specification.md, Example 8 (~line 39315)
Issue: #772
"""

from __future__ import annotations

import json
import sys
import tempfile
from pathlib import Path
from typing import Any

# ---------------------------------------------------------------------------
# Shared DB setup
# ---------------------------------------------------------------------------


def _setup_db() -> Any:
    """Create in-memory SQLite, return session factory."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    from cleveragents.infrastructure.database.models import Base

    engine = create_engine("sqlite:///:memory:", echo=False)
    Base.metadata.create_all(engine)
    session = sessionmaker(bind=engine, expire_on_commit=False)()

    class _NoClose:
        """Keep in-memory SQLite alive across session.close() calls."""

        __slots__ = ("_s",)

        def __init__(self, s: object) -> None:
            object.__setattr__(self, "_s", s)

        def close(self) -> None:
            pass

        def __getattr__(self, n: str) -> object:
            return getattr(object.__getattribute__(self, "_s"), n)

    wrapper = _NoClose(session)
    return lambda: wrapper


# ---------------------------------------------------------------------------
# Fixture directory
# ---------------------------------------------------------------------------

_FIXTURES = Path(__file__).resolve().parent / "fixtures" / "wf08"


# ---------------------------------------------------------------------------
# Mock Terraform operations (deterministic, no real Terraform needed)
# ---------------------------------------------------------------------------


def _mock_terraform_show(resource_address: str) -> dict[str, Any]:
    """Mock ``terraform show`` returning resource state from fixture."""
    tfstate_path = _FIXTURES / "terraform.tfstate.json"
    state = json.loads(tfstate_path.read_text())
    for resource in state.get("resources", []):
        addr = f"{resource['type']}.{resource['name']}"
        if addr == resource_address:
            return {
                "address": addr,
                "type": resource["type"],
                "name": resource["name"],
                "values": resource["instances"][0]["attributes"],
            }
    return {"error": f"Resource {resource_address} not found"}


def _mock_cloud_metrics(resource_id: str, metric: str) -> dict[str, Any]:
    """Mock ``cloud_metrics`` fetching monitoring data from fixture."""
    metrics_path = _FIXTURES / "cloud-metrics.json"
    data = json.loads(metrics_path.read_text())
    for entry in data.get("metrics", []):
        if entry["resource_id"] == resource_id and entry["metric"] == metric:
            return entry
    return {"error": f"No metric {metric} for {resource_id}"}


def _mock_terraform_plan(target: str | None = None) -> dict[str, Any]:
    """Mock ``terraform plan`` returning a plan summary."""
    changes: list[dict[str, str]] = []
    if target is None or target == "aws_instance.web":
        changes.append(
            {
                "action": "update",
                "resource": "aws_instance.web",
                "detail": "instance_type: m5.2xlarge -> m5.large (right-sizing)",
            }
        )
    return {
        "plan_summary": {
            "add": 0,
            "change": len(changes),
            "destroy": 0,
        },
        "changes": changes,
    }


def _analyze_infrastructure() -> dict[str, Any]:
    """Run mocked infrastructure analysis producing optimization recommendations.

    Simulates the LLM-driven analysis of Terraform state and cloud metrics
    to produce cost-saving recommendations.
    """
    # Step 1: Show current web instance state
    web_state = _mock_terraform_show("aws_instance.web")
    assert "error" not in web_state, f"Failed to show web instance: {web_state}"

    # Step 2: Get CPU metrics for the web instance
    web_cpu = _mock_cloud_metrics("i-0abc123def456", "CPUUtilization")
    assert "error" not in web_cpu, f"Failed to get web CPU metrics: {web_cpu}"

    # Step 3: Check API instance (should NOT be recommended for changes — critical)
    api_state = _mock_terraform_show("aws_instance.api")
    assert api_state["values"]["tags"]["critical"] == "true"

    # Step 4: Generate plan for right-sizing
    plan = _mock_terraform_plan("aws_instance.web")

    # Step 5: Build optimization recommendations
    recommendations: list[dict[str, Any]] = []

    # Web instance is over-provisioned: avg CPU 12.3%, peak 28.7% on m5.2xlarge
    if web_cpu["average"] < 30.0 and web_cpu["peak"] < 70.0:
        recommendations.append(
            {
                "resource": "aws_instance.web",
                "type": "right-sizing",
                "current": web_state["values"]["instance_type"],
                "recommended": "m5.large",
                "reason": (
                    f"Average CPU {web_cpu['average']}%, "
                    f"peak {web_cpu['peak']}% — "
                    "well within m5.large capacity"
                ),
                "estimated_monthly_savings_usd": 148.0,
            }
        )

    return {
        "recommendations": recommendations,
        "plan": plan,
        "analyzed_resources": 3,
        "skipped_critical": ["aws_instance.api"],
    }


# ---------------------------------------------------------------------------
# 1. Register custom resource type (Spec Step 1)
# ---------------------------------------------------------------------------


def _register_terraform_resource_type() -> None:
    """Register local/terraform-state resource type via YAML fixture."""
    from cleveragents.application.services.resource_registry_service import (
        ResourceRegistryService,
    )

    factory = _setup_db()
    svc = ResourceRegistryService(session_factory=factory)

    yaml_path = str(_FIXTURES / "terraform-state.yaml")
    spec = svc.register_type(yaml_path)

    assert spec.name == "local/terraform-state", (
        f"Expected name 'local/terraform-state', got '{spec.name}'"
    )
    assert str(spec.sandbox_strategy) == "copy_on_write", (
        f"Expected sandbox_strategy 'copy_on_write', got '{spec.sandbox_strategy}'"
    )
    assert spec.capabilities.get("read") is True
    assert spec.capabilities.get("write") is True
    assert spec.capabilities.get("sandbox") is True
    assert spec.capabilities.get("checkpoint") is True

    # Verify cli_args (state-path required, workspace optional)
    cli_args = spec.cli_args
    assert len(cli_args) == 2, f"Expected 2 cli_args, got {len(cli_args)}"
    state_path_arg = next(a for a in cli_args if a.name == "state-path")
    assert state_path_arg.required is True
    workspace_arg = next(a for a in cli_args if a.name == "workspace")
    assert workspace_arg.required is False
    assert workspace_arg.default == "default"

    # Verify show round-trip
    shown = svc.show_type("local/terraform-state")
    assert shown.name == "local/terraform-state"

    # Register an instance of this type
    with tempfile.TemporaryDirectory(prefix="wf08-tf-") as tmpdir:
        resource = svc.register_resource(
            type_name="local/terraform-state",
            name="local/prod-tfstate",
            location=tmpdir,
            description="Production Terraform state",
            properties={
                "state-path": f"{tmpdir}/terraform.tfstate",
                "workspace": "production",
            },
        )
        assert resource.name == "local/prod-tfstate"
        assert resource.properties["workspace"] == "production"

    print("register-terraform-resource-type-ok")


# ---------------------------------------------------------------------------
# 2. Register skill with tool composition (Spec Step 1)
# ---------------------------------------------------------------------------


def _register_terraform_skill() -> None:
    """Register local/terraform-ops skill with 3 tools and include_skills."""
    from cleveragents.application.services.skill_service import SkillService
    from cleveragents.domain.models.core.skill import Skill

    skill_svc = SkillService()

    # Build skill from config dict matching spec YAML
    skill_config: dict[str, Any] = {
        "name": "local/terraform-ops",
        "description": "Terraform infrastructure operations",
        "anonymous_tools": [
            {
                "description": "Run terraform plan and return the execution plan",
                "source": "custom",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "target": {
                            "type": "string",
                            "description": "Specific resource to target",
                        },
                    },
                    "required": [],
                },
                "capability": {
                    "writes": False,
                    "checkpointable": False,
                },
            },
            {
                "description": "Show current state of a Terraform resource",
                "source": "custom",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "resource_address": {
                            "type": "string",
                            "description": "Terraform resource address",
                        },
                    },
                    "required": ["resource_address"],
                },
                "capability": {
                    "writes": False,
                    "checkpointable": False,
                },
            },
            {
                "description": "Fetch cloud monitoring metrics for a resource",
                "source": "custom",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "resource_id": {"type": "string"},
                        "metric": {
                            "type": "string",
                            "description": "e.g., CPUUtilization",
                        },
                        "period_days": {
                            "type": "integer",
                            "default": 30,
                        },
                    },
                    "required": ["resource_id", "metric"],
                },
                "capability": {
                    "writes": False,
                    "checkpointable": False,
                },
            },
        ],
        # Skill composition — includes local/file-ops per spec
        "includes": ["local/file-ops"],
    }

    skill = Skill.from_config(skill_config)
    assert skill.name == "local/terraform-ops", (
        f"Expected name 'local/terraform-ops', got '{skill.name}'"
    )
    assert skill.description == "Terraform infrastructure operations"

    # Verify 3 anonymous tools (terraform_plan, terraform_show, cloud_metrics)
    assert len(skill.anonymous_tools) == 3, (
        f"Expected 3 anonymous tools, got {len(skill.anonymous_tools)}"
    )

    # Verify all tools are read-only (writes=False)
    for tool in skill.anonymous_tools:
        cap = tool.capability
        assert cap is not None, "Tool capability must not be None"
        assert cap.writes is False, (
            f"Tool '{tool.description}' should have writes=False"
        )
        assert cap.checkpointable is False, (
            f"Tool '{tool.description}' should have checkpointable=False"
        )

    # Verify skill composition (includes)
    assert len(skill.includes) == 1, f"Expected 1 include, got {len(skill.includes)}"
    assert skill.includes[0].name == "local/file-ops"

    # Verify skill_count tracks
    assert skill_svc.skill_count() >= 0

    print("register-terraform-skill-ok")


# ---------------------------------------------------------------------------
# 3. Supervised profile behavior (Spec Step 3 automation)
# ---------------------------------------------------------------------------


def _supervised_profile_behavior() -> None:
    """Verify supervised profile: gates at strategize->execute and execute->apply."""
    from cleveragents.application.services.automation_profile_service import (
        AutomationProfileService,
    )
    from cleveragents.application.services.plan_lifecycle_service import (
        PlanLifecycleService,
    )
    from cleveragents.config.settings import Settings
    from cleveragents.domain.models.core.plan import (
        PlanPhase,
        ProcessingState,
        ProjectLink,
    )

    # Verify supervised profile properties
    profile_svc = AutomationProfileService(repo=None)
    supervised = profile_svc.resolve_profile(plan_profile="supervised")
    assert supervised.name == "supervised"
    assert supervised.create_tool == 1.0, (
        f"Expected create_tool=1.0, got {supervised.create_tool}"
    )
    assert supervised.select_tool == 1.0, (
        f"Expected select_tool=1.0, got {supervised.select_tool}"
    )
    assert supervised.safety.require_sandbox is True
    assert supervised.safety.require_checkpoints is True

    # Verify supervised profile gates plan transitions
    service = PlanLifecycleService(settings=Settings())
    service.create_action(
        name="local/supervised-gate-test",
        description="Test supervised profile gates",
        definition_of_done="Gates verified",
        strategy_actor="local/stub",
        execution_actor="local/stub",
        automation_profile="supervised",
    )
    plan = service.use_action(
        action_name="local/supervised-gate-test",
        project_links=[ProjectLink(project_name="test")],
    )
    pid = plan.identity.plan_id

    # Strategize phase
    service.start_strategize(pid)
    service.complete_strategize(pid)
    plan = service.get_plan(pid)

    # Supervised: create_tool=1.0 means should_auto_progress is False
    assert plan.phase == PlanPhase.STRATEGIZE
    assert plan.processing_state == ProcessingState.COMPLETE
    assert service.should_auto_progress(plan) is False, (
        "Supervised profile should NOT auto-progress from strategize"
    )

    # Explicit execute
    service.execute_plan(pid)
    service.start_execute(pid)
    plan = service.complete_execute(pid)

    # Supervised: select_tool=1.0 means should_auto_progress is False
    assert plan.phase == PlanPhase.EXECUTE
    assert plan.processing_state == ProcessingState.COMPLETE
    assert service.should_auto_progress(plan) is False, (
        "Supervised profile should NOT auto-progress from execute"
    )

    # Explicit apply works
    service.apply_plan(pid)
    service.start_apply(pid)
    plan = service.complete_apply(pid)
    assert plan.processing_state == ProcessingState.APPLIED
    assert plan.is_terminal

    print("supervised-profile-behavior-ok")


# ---------------------------------------------------------------------------
# 4. Action with supervised profile, args, invariants + plan use (Spec Step 3)
# ---------------------------------------------------------------------------


def _create_infra_optimize_action() -> None:
    """Create infra-optimize action and verify plan creation with invariants."""
    from cleveragents.application.services.plan_lifecycle_service import (
        PlanLifecycleService,
    )
    from cleveragents.config.settings import Settings
    from cleveragents.domain.models.core.action import (
        ActionArgument,
        ArgumentRequirement,
        ArgumentType,
    )
    from cleveragents.domain.models.core.plan import (
        InvariantSource,
        PlanPhase,
        ProcessingState,
        ProjectLink,
    )

    service = PlanLifecycleService(settings=Settings())
    action = service.create_action(
        name="local/infra-optimize",
        description="Analyze and optimize cloud infrastructure costs",
        definition_of_done=(
            "- Unused resources identified and removal plans generated\n"
            "- Over-provisioned resources identified with right-sizing recs\n"
            "- terraform validate passes on all changes\n"
            "- terraform plan shows no unexpected resource deletions\n"
            "- Estimated monthly cost savings documented"
        ),
        strategy_actor="local/refactoring-strategist",
        execution_actor="local/infra-executor",
        automation_profile="supervised",
        reusable=True,
        arguments=[
            ActionArgument(
                name="optimization_targets",
                arg_type=ArgumentType.STRING,
                requirement=ArgumentRequirement.OPTIONAL,
                description="Comma-separated: compute, storage, all",
                default_value="all",
            ),
            ActionArgument(
                name="min_savings_threshold",
                arg_type=ArgumentType.FLOAT,
                requirement=ArgumentRequirement.OPTIONAL,
                description="Min monthly savings (USD) to include",
                default_value="10.0",
            ),
        ],
        invariants=[
            "Changes to production databases require manual approval",
            (
                "Instance type downgrades must verify"
                " current peak CPU < 70% of new capacity"
            ),
        ],
    )

    assert str(action.namespaced_name) == "local/infra-optimize"
    assert action.automation_profile == "supervised"
    assert action.reusable is True
    assert len(action.arguments) == 2
    assert len(action.invariants) == 2

    # Use the action to create a plan
    plan = service.use_action(
        action_name="local/infra-optimize",
        project_links=[ProjectLink(project_name="local/infra-prod")],
        arguments={
            "optimization_targets": "compute,storage",
            "min_savings_threshold": 25.0,
        },
    )
    assert plan.phase == PlanPhase.STRATEGIZE
    assert plan.state == ProcessingState.QUEUED

    # Verify arguments flowed to plan
    assert plan.arguments["optimization_targets"] == "compute,storage"
    assert plan.arguments["min_savings_threshold"] == 25.0

    # Verify invariants propagated from action
    action_invariants = [
        inv for inv in plan.invariants if inv.source == InvariantSource.ACTION
    ]
    assert len(action_invariants) == 2, (
        f"Expected 2 action invariants on plan, got {len(action_invariants)}"
    )
    inv_texts = sorted(inv.text for inv in action_invariants)
    expected = sorted(
        [
            "Changes to production databases require manual approval",
            (
                "Instance type downgrades must verify"
                " current peak CPU < 70% of new capacity"
            ),
        ]
    )
    assert inv_texts == expected, f"Invariant texts mismatch: {inv_texts}"

    print("create-infra-optimize-action-ok")


# ---------------------------------------------------------------------------
# 5. Infrastructure analysis produces optimization recommendations
# ---------------------------------------------------------------------------


def _infra_analysis_recommendations() -> None:
    """Verify mocked infrastructure analysis produces valid recommendations."""
    result = _analyze_infrastructure()

    # Must have recommendations
    recs = result["recommendations"]
    assert len(recs) >= 1, f"Expected at least 1 recommendation, got {len(recs)}"

    # First recommendation should be right-sizing the web instance
    web_rec = recs[0]
    assert web_rec["resource"] == "aws_instance.web"
    assert web_rec["type"] == "right-sizing"
    assert web_rec["current"] == "m5.2xlarge"
    assert web_rec["recommended"] == "m5.large"
    assert web_rec["estimated_monthly_savings_usd"] > 0

    # Plan should show no destroys (safe optimization)
    plan = result["plan"]
    assert plan["plan_summary"]["destroy"] == 0, (
        "Optimization plan should have zero destroys"
    )

    # Critical resources should be skipped
    assert "aws_instance.api" in result["skipped_critical"]

    # Total analyzed resources
    assert result["analyzed_resources"] == 3

    print("infra-analysis-recommendations-ok")


# ---------------------------------------------------------------------------
# 6. Invariant enforcement
# ---------------------------------------------------------------------------


def _invariant_enforcement() -> None:
    """Verify invariants block critical resource modifications.

    Simulates the invariant check: "Never delete or modify resources tagged
    with 'critical: true'" — a project-level invariant from the spec.
    """
    from cleveragents.application.services.plan_lifecycle_service import (
        PlanLifecycleService,
    )
    from cleveragents.config.settings import Settings
    from cleveragents.domain.models.core.plan import (
        InvariantSource,
        ProjectLink,
    )

    service = PlanLifecycleService(settings=Settings())
    action = service.create_action(
        name="local/invariant-test-infra",
        description="Invariant enforcement test",
        definition_of_done="Invariants block critical changes",
        strategy_actor="local/stub",
        execution_actor="local/stub",
        automation_profile="supervised",
        invariants=[
            "Never delete or modify resources tagged with 'critical: true'",
            "All changes must be backward-compatible with running services",
            "Cost changes must be documented in the PR description",
        ],
    )
    assert len(action.invariants) == 3

    plan = service.use_action(
        action_name="local/invariant-test-infra",
        project_links=[ProjectLink(project_name="local/infra-prod")],
    )

    # All invariants should flow to the plan
    action_invariants = [
        inv for inv in plan.invariants if inv.source == InvariantSource.ACTION
    ]
    assert len(action_invariants) == 3, (
        f"Expected 3 action invariants, got {len(action_invariants)}"
    )

    # Simulate invariant check: critical resource modification blocked
    api_state = _mock_terraform_show("aws_instance.api")
    is_critical = api_state["values"]["tags"].get("critical") == "true"
    assert is_critical, "API instance must be tagged as critical"

    # Verify that the invariant text matches what we'd check against
    inv_texts = [inv.text for inv in action_invariants]
    critical_invariant = next((t for t in inv_texts if "critical" in t.lower()), None)
    assert critical_invariant is not None, (
        "Must have invariant referencing critical resources"
    )

    # Mock enforcement: if resource is critical, block the change
    change_blocked = is_critical and critical_invariant is not None
    assert change_blocked, (
        "Critical resource modification should be blocked by invariant"
    )

    # Non-critical resource should NOT be blocked
    web_state = _mock_terraform_show("aws_instance.web")
    web_is_critical = web_state["values"]["tags"].get("critical") == "true"
    assert not web_is_critical, "Web instance should NOT be critical"
    web_blocked = web_is_critical and critical_invariant is not None
    assert not web_blocked, "Non-critical resource modification should NOT be blocked"

    print("invariant-enforcement-ok")


# ---------------------------------------------------------------------------
# Command dispatch
# ---------------------------------------------------------------------------

_COMMANDS: dict[str, Any] = {
    "register-terraform-resource-type": _register_terraform_resource_type,
    "register-terraform-skill": _register_terraform_skill,
    "supervised-profile-behavior": _supervised_profile_behavior,
    "create-infra-optimize-action": _create_infra_optimize_action,
    "infra-analysis-recommendations": _infra_analysis_recommendations,
    "invariant-enforcement": _invariant_enforcement,
}

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <command>")
        print(f"Commands: {', '.join(sorted(_COMMANDS))}")
        sys.exit(1)

    cmd = sys.argv[1]
    fn = _COMMANDS.get(cmd)
    if fn is None:
        print(f"Unknown command: {cmd}")
        sys.exit(1)

    try:
        fn()
    except Exception as exc:
        print(f"FAIL [{cmd}]: {type(exc).__name__}: {exc}")
        import traceback

        traceback.print_exc()
        sys.exit(1)
