"""Step definitions for tui_persona_registry_coverage.feature.

These steps target specific uncovered lines in registry.py:
- Line 78:  persona_path raises ValueError for path-traversal name
- Line 110: list_personas skips non-dict YAML files
- Line 122: get returns None for non-dict YAML
- Line 130: _validate_cycle_order skips self (existing.name == persona.name)
- Lines 154-155: delete returns False when file doesn't exist
- Line 163: export_persona raises ValueError for missing persona
- Lines 166-167: export_persona happy path (atomic write + return)
- Lines 171-176: import_persona full path (read, validate, save, return)
- Line 183: load_state returns {} for non-dict YAML state file
"""

from __future__ import annotations

import os
import shutil
import tempfile
from pathlib import Path
from unittest.mock import patch

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

from cleveragents.tui.persona.registry import PersonaRegistry
from cleveragents.tui.persona.schema import Persona


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("a fresh temporary persona registry")
def step_fresh_temp_registry(context: Context) -> None:
    temp_dir = Path(tempfile.mkdtemp())
    context.reg_temp_dir = temp_dir
    context.reg = PersonaRegistry(config_dir=temp_dir)
    context.reg.ensure_dirs()
    context.add_cleanup(lambda: shutil.rmtree(str(temp_dir), ignore_errors=True))


# ---------------------------------------------------------------------------
# Scenario: persona_path rejects name that resolves outside personas dir (line 78)
# ---------------------------------------------------------------------------
@when("I call persona_path with a path-traversal name bypassing validation")
def step_persona_path_traversal(context: Context) -> None:
    context.reg_error = None
    # Bypass Persona.validate_name_safe so the traversal name reaches the
    # is_relative_to guard on line 77-78.
    with patch.object(Persona, "validate_name_safe", side_effect=lambda v: v):
        try:
            context.reg.persona_path("../../etc/passwd")
        except ValueError as exc:
            context.reg_error = exc


@then('persona_path should raise ValueError with "Invalid persona name"')
def step_persona_path_error(context: Context) -> None:
    assert context.reg_error is not None, "Expected ValueError but none was raised"
    assert "Invalid persona name" in str(context.reg_error)


# ---------------------------------------------------------------------------
# Scenario: list_personas skips non-dict YAML (line 110)
# ---------------------------------------------------------------------------
@given('I create a persona YAML file "{name}" with content that parses to a list')
def step_create_list_yaml(context: Context, name: str) -> None:
    file = context.reg.personas_dir / f"{name}.yaml"
    file.write_text("- item1\n- item2\n", encoding="utf-8")


@given('I save a valid registry persona "{name}" with actor "{actor}"')
def step_save_valid_persona(context: Context, name: str, actor: str) -> None:
    persona = Persona(name=name, actor=actor)
    context.reg.save(persona)


@when("I list all registry personas")
def step_list_all_personas(context: Context) -> None:
    context.reg_persona_list = context.reg.list_personas()
    context.reg_persona_names = [p.name for p in context.reg_persona_list]


@then('the registry persona list should contain "{name}"')
def step_list_contains(context: Context, name: str) -> None:
    assert name in context.reg_persona_names, (
        f"Expected '{name}' in {context.reg_persona_names}"
    )


@then('the registry persona list should not contain "{name}"')
def step_list_not_contains(context: Context, name: str) -> None:
    assert name not in context.reg_persona_names, (
        f"Did not expect '{name}' in {context.reg_persona_names}"
    )


# ---------------------------------------------------------------------------
# Scenario: get returns None for non-dict YAML (line 122)
# ---------------------------------------------------------------------------
@given('I create a persona YAML file "{name}" with content that parses to a string')
def step_create_string_yaml(context: Context, name: str) -> None:
    file = context.reg.personas_dir / f"{name}.yaml"
    file.write_text("just a plain string\n", encoding="utf-8")


@when('I get persona "{name}" from the registry')
def step_get_persona(context: Context, name: str) -> None:
    context.reg_get_result = context.reg.get(name)


@then("the get result should be None")
def step_get_result_none(context: Context) -> None:
    assert context.reg_get_result is None


# ---------------------------------------------------------------------------
# Scenario: _validate_cycle_order skips self (line 130)
# ---------------------------------------------------------------------------
@given(
    'I save a valid registry persona "{name}" with actor "{actor}" and cycle order {order:d}'
)
def step_save_persona_cycle_order(
    context: Context, name: str, actor: str, order: int
) -> None:
    persona = Persona(name=name, actor=actor, cycle_order=order)
    context.reg.save(persona)


@when(
    'I re-save registry persona "{name}" with actor "{actor}" and cycle order {order:d}'
)
def step_resave_persona(context: Context, name: str, actor: str, order: int) -> None:
    context.reg_resave_error = None
    try:
        persona = Persona(name=name, actor=actor, cycle_order=order)
        context.reg.save(persona)
    except Exception as exc:
        context.reg_resave_error = exc


@then("the re-save should succeed without error")
def step_resave_no_error(context: Context) -> None:
    assert context.reg_resave_error is None, (
        f"Expected no error, got {context.reg_resave_error!r}"
    )


# ---------------------------------------------------------------------------
# Scenario: delete returns False for non-existent persona (lines 154-155)
# ---------------------------------------------------------------------------
@when('I delete persona "{name}" from the registry')
def step_delete_persona(context: Context, name: str) -> None:
    context.reg_delete_result = context.reg.delete(name)


@then("tprcov the delete result should be False")
def step_delete_result_false(context: Context) -> None:
    assert context.reg_delete_result is False


# ---------------------------------------------------------------------------
# Scenario: export_persona raises ValueError for missing persona (line 163)
# ---------------------------------------------------------------------------
@when('I export persona "{name}" to "{output_path}"')
def step_export_persona_missing(context: Context, name: str, output_path: str) -> None:
    context.reg_export_error = None
    try:
        context.reg.export_persona(name, Path(output_path))
    except ValueError as exc:
        context.reg_export_error = exc


@then('the export should raise ValueError with "Persona not found"')
def step_export_not_found(context: Context) -> None:
    assert context.reg_export_error is not None, (
        "Expected ValueError but none was raised"
    )
    assert "Persona not found" in str(context.reg_export_error)


# ---------------------------------------------------------------------------
# Scenario: export_persona happy path (lines 166-167)
# ---------------------------------------------------------------------------
@when('I export persona "{name}" to a valid relative path')
def step_export_persona_happy(context: Context, name: str) -> None:
    context.reg_export_error = None
    # Create a temp working directory so resolve_export_path works
    work_dir = Path(tempfile.mkdtemp())
    context.add_cleanup(lambda: shutil.rmtree(str(work_dir), ignore_errors=True))
    original_cwd = os.getcwd()
    os.chdir(str(work_dir))
    context.add_cleanup(lambda: os.chdir(original_cwd))
    try:
        result = context.reg.export_persona(name, Path("exported.yaml"))
        context.reg_export_path = result
    except Exception as exc:
        context.reg_export_error = exc
        context.reg_export_path = None


@then("the exported file should exist and contain persona data")
def step_exported_file_exists(context: Context) -> None:
    assert context.reg_export_error is None, (
        f"Export failed: {context.reg_export_error!r}"
    )
    assert context.reg_export_path is not None
    assert context.reg_export_path.exists()
    data = yaml.safe_load(context.reg_export_path.read_text(encoding="utf-8"))
    assert isinstance(data, dict)
    assert "name" in data


# ---------------------------------------------------------------------------
# Scenario: import_persona full path (lines 171-176)
# ---------------------------------------------------------------------------
@given("I create a valid persona YAML file at a relative path for import")
def step_create_import_file(context: Context) -> None:
    work_dir = Path(tempfile.mkdtemp())
    context.add_cleanup(lambda: shutil.rmtree(str(work_dir), ignore_errors=True))
    original_cwd = os.getcwd()
    os.chdir(str(work_dir))
    context.add_cleanup(lambda: os.chdir(original_cwd))

    persona_data = {
        "name": "imported-test",
        "actor": "local/mock-actor",
        "description": "A test persona for import",
    }
    import_file = work_dir / "to_import.yaml"
    with open(import_file, "w", encoding="utf-8") as f:
        yaml.safe_dump(persona_data, f)
    context.reg_import_rel_path = Path("to_import.yaml")


@when("I import a persona from the relative path")
def step_import_persona(context: Context) -> None:
    context.reg_import_error = None
    try:
        context.reg_imported_persona = context.reg.import_persona(
            context.reg_import_rel_path
        )
    except Exception as exc:
        context.reg_import_error = exc
        context.reg_imported_persona = None


@then("the imported persona should be saved in the registry")
def step_imported_saved(context: Context) -> None:
    assert context.reg_import_error is None, (
        f"Import failed: {context.reg_import_error!r}"
    )
    assert context.reg_imported_persona is not None


@then('the imported persona name should be "{name}"')
def step_imported_name(context: Context, name: str) -> None:
    assert context.reg_imported_persona.name == name
    # Also verify it was persisted to the registry
    loaded = context.reg.get(name)
    assert loaded is not None
    assert loaded.name == name


# ---------------------------------------------------------------------------
# Scenario: import_persona raises ValueError for non-dict (lines 172-173)
# ---------------------------------------------------------------------------
@given("I create a non-dict YAML file at a relative path for import")
def step_create_nondict_import_file(context: Context) -> None:
    work_dir = Path(tempfile.mkdtemp())
    context.add_cleanup(lambda: shutil.rmtree(str(work_dir), ignore_errors=True))
    original_cwd = os.getcwd()
    os.chdir(str(work_dir))
    context.add_cleanup(lambda: os.chdir(original_cwd))

    import_file = work_dir / "bad_import.yaml"
    import_file.write_text("- just\n- a\n- list\n", encoding="utf-8")
    context.reg_import_rel_path = Path("bad_import.yaml")


@when("I import a persona from the non-dict file")
def step_import_nondict(context: Context) -> None:
    context.reg_import_error = None
    try:
        context.reg.import_persona(context.reg_import_rel_path)
    except ValueError as exc:
        context.reg_import_error = exc


@then('the import should raise ValueError with "Invalid persona file"')
def step_import_invalid(context: Context) -> None:
    assert context.reg_import_error is not None, (
        "Expected ValueError but none was raised"
    )
    assert "Invalid persona file" in str(context.reg_import_error)


# ---------------------------------------------------------------------------
# Scenario: load_state returns {} for non-dict YAML (line 183)
# ---------------------------------------------------------------------------
@given("I write non-dict YAML to the state file")
def step_write_nondict_state(context: Context) -> None:
    context.reg.ensure_dirs()
    context.reg.state_path.write_text("- not\n- a\n- dict\n", encoding="utf-8")


@when("I load registry state")
def step_load_state(context: Context) -> None:
    context.reg_state = context.reg.load_state()


@then("the loaded state should be an empty dict")
def step_state_empty_dict(context: Context) -> None:
    assert context.reg_state == {}
    assert isinstance(context.reg_state, dict)
