"""Component tests for persona file loading via CLI.

Tests the full path from `--persona` argument through persona loading
to PersonaSpec construction. Mocks execution layer to avoid real LLM calls.

Moved from e2e scope to component scope to improve seam purity.
Mocks boundary seam (AgentRunner) while testing CLI argument parsing.

Spec source: vectl step `e2e-markdown-persona`
"""

import json
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from click.testing import CliRunner
from returns.result import Success

from anima.core.schemas import AgentResult, AgentState, TokenUsage
from anima.shell.cli import main


# =============================================================================
# Fixtures
# =============================================================================


@pytest.fixture
def cli_runner() -> CliRunner:
    """Create a CliRunner instance for testing."""
    return CliRunner()


@pytest.fixture
def tmp_state_dir(tmp_path: Path) -> Path:
    """Create temporary state directory for agent state files."""
    state_dir = tmp_path / "state"
    state_dir.mkdir()
    return state_dir


@pytest.fixture
def claude_code_persona(tmp_path: Path) -> Path:
    """Create Claude Code format persona with id in frontmatter."""
    content = """---
id: claude-code-helper
model: gpt-4
---
You are a concise coding assistant.
"""
    file_path = tmp_path / "claude-code-agent.md"
    file_path.write_text(content)
    return file_path


@pytest.fixture
def opencode_persona(tmp_path: Path) -> Path:
    """Create OpenCode format persona (no id, uses filename)."""
    content = """---
model: gpt-4
---
You are a general-purpose assistant.
"""
    file_path = tmp_path / "opencode-agent.md"
    file_path.write_text(content)
    return file_path


@pytest.fixture
def json_persona(tmp_path: Path) -> Path:
    """Create JSON format persona for regression test."""
    persona = {
        "id": "json-test-agent",
        "prompt": "You are a JSON persona assistant.",
        "model": "gpt-4",
        "spec_version": "0.1.0",
    }
    file_path = tmp_path / "persona.json"
    file_path.write_text(json.dumps(persona))
    return file_path


@pytest.fixture
def opencode_unknown_field_persona(tmp_path: Path) -> Path:
    """Create OpenCode persona with unknown frontmatter field and no id."""
    content = """---
description: A helpful research assistant
model: gpt-4
---
You are a research-focused AI assistant.
"""
    file_path = tmp_path / "research-agent.md"
    file_path.write_text(content)
    return file_path


@pytest.fixture
def opencode_description_only_persona(tmp_path: Path) -> Path:
    """Create OpenCode persona with description field and no body (empty body)."""
    content = """---
model: gpt-4
description: A coding assistant for Python development
---
"""
    file_path = tmp_path / "python-helper.md"
    file_path.write_text(content)
    return file_path


# =============================================================================
# Tests
# =============================================================================


@patch("anima.shell.agent.AgentRunner")
def test_run_with_md_persona_frontmatter_name(
    mock_runner_class: MagicMock,
    cli_runner: CliRunner,
    tmp_state_dir: Path,
    claude_code_persona: Path,
) -> None:
    """Markdown persona with frontmatter name loads correctly.

    Verifies:
    - ID from frontmatter: "claude-code-helper"
    - System prompt from body: "You are a concise coding assistant."
    - No real LLM calls (mocked execution)
    """
    # Mock AgentRunner to return success
    mock_runner = MagicMock()
    mock_runner.run = AsyncMock(
        return_value=Success(
            AgentResult(
                final_state=AgentState.completed,
                token_usage=TokenUsage(prompt_tokens=10, completion_tokens=5),
            )
        )
    )
    mock_runner_class.return_value = mock_runner

    # Invoke run command with markdown persona
    result = cli_runner.invoke(
        main,
        [
            "run",
            "hello",
            "--persona",
            str(claude_code_persona),
            "--model",
            "gpt-4",
            "--json",
        ],
        env={"ANIMA_STATE": str(tmp_state_dir)},
        catch_exceptions=False,
    )

    # Check exit code
    assert result.exit_code == 0

    # Verify AgentRunner was constructed with correct persona
    assert mock_runner_class.called
    call_kwargs = mock_runner_class.call_args[1]
    persona = call_kwargs.get("persona")

    # Verify PersonaSpec fields
    assert persona is not None
    assert persona.id == "claude-code-helper"
    assert persona.prompt == "You are a concise coding assistant."

    # Verify JSON output
    output = json.loads(result.output.strip())
    assert output["final_state"] == "completed"


@patch("anima.shell.agent.AgentRunner")
def test_run_with_md_persona_filename_fallback(
    mock_runner_class: MagicMock,
    cli_runner: CliRunner,
    tmp_state_dir: Path,
    opencode_persona: Path,
) -> None:
    """Markdown persona without id in frontmatter uses filename stem.

    Verifies:
    - ID from filename: "opencode-agent"
    - System prompt from body: "You are a general-purpose assistant."
    - No real LLM calls (mocked execution)
    """
    # Mock AgentRunner to return success
    mock_runner = MagicMock()
    mock_runner.run = AsyncMock(
        return_value=Success(
            AgentResult(
                final_state=AgentState.completed,
                token_usage=TokenUsage(prompt_tokens=10, completion_tokens=5),
            )
        )
    )
    mock_runner_class.return_value = mock_runner

    # Invoke run command with markdown persona
    result = cli_runner.invoke(
        main,
        [
            "run",
            "hello",
            "--persona",
            str(opencode_persona),
            "--model",
            "gpt-4",
            "--json",
        ],
        env={"ANIMA_STATE": str(tmp_state_dir)},
        catch_exceptions=False,
    )

    # Check exit code
    assert result.exit_code == 0

    # Verify AgentRunner was constructed with correct persona
    assert mock_runner_class.called
    call_kwargs = mock_runner_class.call_args[1]
    persona = call_kwargs.get("persona")

    # Verify PersonaSpec fields
    assert persona is not None
    assert persona.id == "opencode-agent"  # Fallback to filename stem
    assert persona.prompt == "You are a general-purpose assistant."

    # Verify JSON output
    output = json.loads(result.output.strip())
    assert output["final_state"] == "completed"


@patch("anima.shell.agent.AgentRunner")
def test_run_with_json_persona_still_works(
    mock_runner_class: MagicMock,
    cli_runner: CliRunner,
    tmp_state_dir: Path,
    json_persona: Path,
) -> None:
    """JSON persona files continue to work (regression guard).

    Verifies:
    - JSON persona loading unchanged
    - No breaking changes to existing functionality
    - No real LLM calls (mocked execution)
    """
    # Mock AgentRunner to return success
    mock_runner = MagicMock()
    mock_runner.run = AsyncMock(
        return_value=Success(
            AgentResult(
                final_state=AgentState.completed,
                token_usage=TokenUsage(prompt_tokens=10, completion_tokens=5),
            )
        )
    )
    mock_runner_class.return_value = mock_runner

    # Invoke run command with JSON persona
    result = cli_runner.invoke(
        main,
        [
            "run",
            "hello",
            "--persona",
            str(json_persona),
            "--json",
        ],
        env={"ANIMA_STATE": str(tmp_state_dir)},
        catch_exceptions=False,
    )

    # Check exit code
    assert result.exit_code == 0

    # Verify AgentRunner was constructed with correct persona
    assert mock_runner_class.called
    call_kwargs = mock_runner_class.call_args[1]
    persona = call_kwargs.get("persona")

    # Verify PersonaSpec fields from JSON
    assert persona is not None
    assert persona.id == "json-test-agent"
    assert persona.prompt == "You are a JSON persona assistant."
    assert persona.model == "gpt-4"

    # Verify JSON output
    output = json.loads(result.output.strip())
    assert output["final_state"] == "completed"


@patch("anima.shell.agent.AgentRunner")
def test_run_with_md_persona_unknown_field(
    mock_runner_class: MagicMock,
    cli_runner: CliRunner,
    tmp_state_dir: Path,
    opencode_unknown_field_persona: Path,
) -> None:
    """Markdown persona with unknown field loads and id falls back to filename.

    Verifies OpenCode-style persona with:
    - Unknown `description` field (ignored, passed through)
    - No `id` field (fallback to filename stem: "research-agent")
    - System prompt loaded correctly

    No real LLM calls (mocked execution).
    """
    # Mock AgentRunner to return success
    mock_runner = MagicMock()
    mock_runner.run = AsyncMock(
        return_value=Success(
            AgentResult(
                final_state=AgentState.completed,
                token_usage=TokenUsage(prompt_tokens=10, completion_tokens=5),
            )
        )
    )
    mock_runner_class.return_value = mock_runner

    # Invoke run command with markdown persona
    result = cli_runner.invoke(
        main,
        [
            "run",
            "hello",
            "--persona",
            str(opencode_unknown_field_persona),
            "--model",
            "gpt-4",
            "--json",
        ],
        env={"ANIMA_STATE": str(tmp_state_dir)},
        catch_exceptions=False,
    )

    # Check exit code
    if result.exit_code != 0:
        print(f"Exit code: {result.exit_code}")
        print(f"Output: {result.output}")
        if result.exception:
            print(f"Exception: {result.exception}")
    assert result.exit_code == 0

    # Verify AgentRunner was constructed with correct persona
    assert mock_runner_class.called
    call_kwargs = mock_runner_class.call_args[1]
    persona = call_kwargs.get("persona")

    # Verify PersonaSpec fields
    assert persona is not None
    assert persona.id == "research-agent"  # Fallback to filename stem
    assert persona.prompt == "You are a research-focused AI assistant."

    # Verify JSON output
    output = json.loads(result.output.strip())
    assert output["final_state"] == "completed"


@patch("anima.shell.agent.AgentRunner")
def test_run_with_md_persona_description_only_empty_body(
    mock_runner_class: MagicMock,
    cli_runner: CliRunner,
    tmp_state_dir: Path,
    opencode_description_only_persona: Path,
) -> None:
    """Markdown persona with description-only and empty body.

    Verifies OpenCode-style persona with:
    - Unknown `description` field (used as system_prompt fallback)
    - Empty body (description takes precedence)
    - No `id` field (fallback to filename stem: "python-helper")
    - No real LLM calls (mocked execution)
    """
    # Mock AgentRunner to return success
    mock_runner = MagicMock()
    mock_runner.run = AsyncMock(
        return_value=Success(
            AgentResult(
                final_state=AgentState.completed,
                token_usage=TokenUsage(prompt_tokens=10, completion_tokens=5),
            )
        )
    )
    mock_runner_class.return_value = mock_runner

    # Invoke run command with markdown persona
    result = cli_runner.invoke(
        main,
        [
            "run",
            "hello",
            "--persona",
            str(opencode_description_only_persona),
            "--model",
            "gpt-4",
            "--json",
        ],
        env={"ANIMA_STATE": str(tmp_state_dir)},
        catch_exceptions=False,
    )

    # Check exit code
    if result.exit_code != 0:
        print(f"Exit code: {result.exit_code}")
        print(f"Output: {result.output}")
        if result.exception:
            print(f"Exception: {result.exception}")
    assert result.exit_code == 0

    # Verify AgentRunner was constructed with correct persona
    assert mock_runner_class.called
    call_kwargs = mock_runner_class.call_args[1]
    persona = call_kwargs.get("persona")

    # Verify PersonaSpec fields
    assert persona is not None
    assert persona.id == "python-helper"  # Fallback to filename stem
    assert persona.prompt == "A coding assistant for Python development"

    # Verify JSON output
    output = json.loads(result.output.strip())
    assert output["final_state"] == "completed"
