"""Steps for TDD bug-capture scenario #989.

This file implements the Behave steps for bug #989 — corrupt JSON in
automation profile persistence. The ``@tdd_expected_fail`` tag has been
removed because the bugfix is included. These steps now verify the
corrected behavior: a ``CorruptRecordError`` is raised instead of a raw
``json.JSONDecodeError``.
"""

from __future__ import annotations

import json
from datetime import UTC, datetime
from typing import Any

from behave import given, then, when
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from cleveragents.infrastructure.database.models import (
    AutomationProfileModel,
    Base,
)
from cleveragents.infrastructure.database.repositories import (
    AutomationProfileRepository,
    CorruptRecordError,
)


def _now_iso() -> str:
    return datetime.now(UTC).isoformat()


@given("an automation profile repository row with corrupt safety_json for bug 989")
def step_given_corrupt_safety_json_row(context: Any) -> None:
    """Persist a row that reproduces the malformed JSON decode path in _to_domain."""
    engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
    Base.metadata.create_all(bind=engine)
    factory = sessionmaker(bind=engine, expire_on_commit=False, future=True)

    now_iso = _now_iso()
    with factory() as session:
        session.add(
            AutomationProfileModel(
                name="bug-989-corrupt-json",
                description="Corrupt JSON regression fixture",
                schema_version="1.0",
                decompose_task=0.0,
                create_tool=0.0,
                select_tool=0.0,
                edit_code=0.0,
                execute_command=0.0,
                create_file=0.0,
                delete_content=0.0,
                access_network=0.0,
                install_dependency=0.0,
                modify_config=0.0,
                approve_plan=0.0,
                require_sandbox=True,
                require_checkpoints=True,
                allow_unsafe_tools=False,
                # Deliberately malformed JSON (missing closing brace)
                safety_json='{"require_sandbox": true',
                guards_json=None,
                created_at=now_iso,
                updated_at=now_iso,
            )
        )
        session.commit()

    context.tdd_989_repo = AutomationProfileRepository(factory)
    context.tdd_989_error = None


@when("the corrupt automation profile is fetched by name for bug 989")
def step_when_fetch_corrupt_profile(context: Any) -> None:
    """Execute repository get path that wraps JSONDecodeError in CorruptRecordError."""
    repo: AutomationProfileRepository = context.tdd_989_repo
    try:
        repo.get_by_name("bug-989-corrupt-json")
    except Exception as exc:
        context.tdd_989_error = exc


@then(
    "a corruption-specific domain error should be raised instead of JSONDecodeError for bug 989"
)
def step_then_domain_specific_error(context: Any) -> None:
    """Assert that a CorruptRecordError is raised, not a raw JSONDecodeError."""
    err = context.tdd_989_error
    assert err is not None, (
        "Expected a corruption-specific domain error, but no error was raised"
    )
    assert not isinstance(err, json.JSONDecodeError), (
        "Expected a domain-specific corruption error, "
        f"but raw JSONDecodeError leaked: {err}"
    )
    assert isinstance(err, CorruptRecordError), (
        f"Expected CorruptRecordError, got {type(err).__name__}: {err}"
    )
    name_or_message = f"{type(err).__name__} {err}".lower()
    assert "corrupt" in name_or_message, (
        "Expected corruption-specific error semantics "
        f"(type/message containing 'corrupt'), got {type(err).__name__}: {err}"
    )


@given("an automation profile repository row with corrupt guards_json for bug 989")
def step_given_corrupt_guards_json_row(context: Any) -> None:
    """Persist a row with valid safety_json but corrupt guards_json."""
    engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
    Base.metadata.create_all(bind=engine)
    factory = sessionmaker(bind=engine, expire_on_commit=False, future=True)

    now_iso = _now_iso()
    with factory() as session:
        session.add(
            AutomationProfileModel(
                name="bug-989-corrupt-guards",
                description="Corrupt guards_json regression fixture",
                schema_version="1.0",
                decompose_task=0.0,
                create_tool=0.0,
                select_tool=0.0,
                edit_code=0.0,
                execute_command=0.0,
                create_file=0.0,
                delete_content=0.0,
                access_network=0.0,
                install_dependency=0.0,
                modify_config=0.0,
                approve_plan=0.0,
                require_sandbox=True,
                require_checkpoints=True,
                allow_unsafe_tools=False,
                safety_json=None,
                # Deliberately malformed JSON
                guards_json="{not valid json!",
                created_at=now_iso,
                updated_at=now_iso,
            )
        )
        session.commit()

    context.tdd_989_guards_repo = AutomationProfileRepository(factory)
    context.tdd_989_guards_error = None


@when("the corrupt guards automation profile is fetched by name for bug 989")
def step_when_fetch_corrupt_guards_profile(context: Any) -> None:
    """Execute repository get path for corrupt guards_json."""
    repo: AutomationProfileRepository = context.tdd_989_guards_repo
    try:
        repo.get_by_name("bug-989-corrupt-guards")
    except Exception as exc:
        context.tdd_989_guards_error = exc


@then("a corruption-specific domain error should be raised for guards_json for bug 989")
def step_then_guards_domain_specific_error(context: Any) -> None:
    """Assert CorruptRecordError is raised for corrupt guards_json."""
    err = context.tdd_989_guards_error
    assert err is not None, (
        "Expected a CorruptRecordError for corrupt guards_json, but no error was raised"
    )
    assert isinstance(err, CorruptRecordError), (
        f"Expected CorruptRecordError, got {type(err).__name__}: {err}"
    )
    assert err.field == "guards_json", (
        f"Expected field='guards_json', got field='{err.field}'"
    )


@given("a domain profile object with malformed safety data for bug 989")
def step_given_malformed_safety_profile(context: Any) -> None:
    """Create a mock profile with safety that cannot be serialized."""

    class _BrokenSafety:
        """Safety object whose model_dump raises TypeError."""

        require_sandbox: bool = True
        require_checkpoints: bool = True
        allow_unsafe_tools: bool = False

        def model_dump(self) -> None:
            raise TypeError("model_dump intentionally broken for bug 989 test")

    class _FakeProfile:
        """Minimal profile stub with broken safety."""

        name: str = "bug-989-malformed"
        description: str = "malformed safety"
        schema_version: str = "1.0"
        decompose_task: float = 0.0
        create_tool: float = 0.0
        select_tool: float = 0.0
        edit_code: float = 0.0
        execute_command: float = 0.0
        create_file: float = 0.0
        delete_content: float = 0.0
        access_network: float = 0.0
        install_dependency: float = 0.0
        modify_config: float = 0.0
        approve_plan: float = 0.0
        safety: _BrokenSafety = _BrokenSafety()
        guards: None = None

    context.tdd_989_malformed_profile = _FakeProfile()
    context.tdd_989_from_domain_error = None


@when("_from_domain is called with the malformed profile for bug 989")
def step_when_from_domain_malformed(context: Any) -> None:
    """Call _from_domain with a profile that will fail serialization."""
    try:
        AutomationProfileRepository._from_domain(
            context.tdd_989_malformed_profile, _now_iso()
        )
    except Exception as exc:
        context.tdd_989_from_domain_error = exc


@then(
    "a corruption-specific domain error should be raised for the serialization for bug 989"
)
def step_then_from_domain_error(context: Any) -> None:
    """Assert CorruptRecordError is raised when _from_domain fails to serialize."""
    err = context.tdd_989_from_domain_error
    assert err is not None, (
        "Expected a CorruptRecordError from _from_domain, but no error was raised"
    )
    assert isinstance(err, CorruptRecordError), (
        f"Expected CorruptRecordError, got {type(err).__name__}: {err}"
    )
    assert err.field == "safety", f"Expected field='safety', got field='{err.field}'"
