"""
Integration tests for authoring tools.

Tests:
- Tool registration and availability
- Hierarchy integrity after CRUD operations
- Response envelope compliance (response-v2)
- End-to-end authoring workflows
"""

import json
from unittest.mock import patch

import pytest
from tests.conftest import extract_response_dict


# =============================================================================
# Test Fixtures
# =============================================================================


@pytest.fixture
def test_specs_dir(tmp_path):
    """Create a test specs directory with sample spec for authoring tests."""
    specs_dir = tmp_path / "specs"
    specs_dir.mkdir()
    (specs_dir / "active").mkdir()
    (specs_dir / "pending").mkdir()
    (specs_dir / "completed").mkdir()
    (specs_dir / "archived").mkdir()
    (specs_dir / "templates").mkdir()

    # Create a sample spec with complete hierarchy for authoring tests
    sample_spec = {
        "spec_id": "authoring-test-spec-001",
        "title": "Authoring Test Specification",
        "metadata": {
            "title": "Authoring Test Specification",
            "description": "A test spec for authoring tool integration testing",
            "created_at": "2025-01-25T00:00:00Z",
            "status": "in_progress",
            "version": "1.0.0",
        },
        "hierarchy": {
            "spec-root": {
                "type": "spec",
                "title": "Authoring Test Specification",
                "status": "in_progress",
                "children": ["phase-1", "phase-2"],
            },
            "phase-1": {
                "type": "phase",
                "title": "Setup Phase",
                "status": "completed",
                "parent": "spec-root",
                "children": ["task-1-1", "task-1-2"],
            },
            "phase-2": {
                "type": "phase",
                "title": "Implementation Phase",
                "status": "in_progress",
                "parent": "spec-root",
                "children": ["task-2-1"],
            },
            "task-1-1": {
                "type": "task",
                "title": "Initial setup",
                "status": "completed",
                "parent": "phase-1",
                "children": [],
            },
            "task-1-2": {
                "type": "task",
                "title": "Configuration",
                "status": "completed",
                "parent": "phase-1",
                "children": [],
            },
            "task-2-1": {
                "type": "task",
                "title": "Main implementation",
                "status": "in_progress",
                "parent": "phase-2",
                "children": ["subtask-2-1-1"],
            },
            "subtask-2-1-1": {
                "type": "subtask",
                "title": "Helper function",
                "status": "pending",
                "parent": "task-2-1",
                "children": [],
            },
        },
        "assumptions": [
            {
                "id": "assumption-1",
                "text": "API is available",
                "type": "constraint",
                "created_at": "2025-01-25T00:00:00Z",
            },
        ],
        "revision_history": [
            {
                "version": "1.0.0",
                "date": "2025-01-25",
                "changes": "Initial version",
                "author": "test",
            },
        ],
        "journal": [
            {
                "timestamp": "2025-01-25T00:00:00Z",
                "entry_type": "status_change",
                "title": "Spec created",
                "content": "Initial spec creation",
                "task_id": "spec-root",
            },
        ],
    }

    spec_file = specs_dir / "active" / "authoring-test-spec-001.json"
    with open(spec_file, "w") as f:
        json.dump(sample_spec, f, indent=2)

    return specs_dir


@pytest.fixture(autouse=True)
def maintainer_role():
    """Run integration authoring tool assertions with maintainer access."""
    with patch(
        "foundry_mcp.tools.unified.common.get_server_role",
        return_value="maintainer",
    ):
        yield


def _call_tool(tools, tool_name: str, **kwargs):
    """Call unified routers for authoring workflows.

    Returns extracted dict from TextContent response for test compatibility.
    """

    if tool_name in {
        "spec-create",
        "phase-add",
        "phase-remove",
        "assumption-add",
        "assumption-list",
        "revision-add",
        "spec-update-frontmatter",
    }:
        result = tools["authoring"].fn(action=tool_name, **kwargs)
        return extract_response_dict(result)

    if tool_name == "spec-template":
        template_action = kwargs.pop("action", None)
        result = tools["authoring"].fn(
            action="spec-template",
            template_action=template_action,
            **kwargs,
        )
        return extract_response_dict(result)

    if tool_name == "task-add":
        if "hours" in kwargs and "estimated_hours" not in kwargs:
            kwargs["estimated_hours"] = kwargs.pop("hours")
        result = tools["task"].fn(action="add", **kwargs)
        return extract_response_dict(result)

    if tool_name == "task-remove":
        result = tools["task"].fn(action="remove", **kwargs)
        return extract_response_dict(result)

    raise KeyError(f"Unsupported tool mapping for {tool_name}")


# =============================================================================
# Authoring Tools Registration Tests
# =============================================================================


class TestAuthoringToolsRegistration:
    """Test that all authoring tools are properly registered."""

    def test_spec_create_registered(self, mcp_server):
        """Test that spec creation entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_spec_template_registered(self, mcp_server):
        """Test that spec template entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_task_add_registered(self, mcp_server):
        """Test that task add entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "task" in tools
        assert callable(tools["task"].fn)

    def test_phase_add_registered(self, mcp_server):
        """Test that phase add entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_task_remove_registered(self, mcp_server):
        """Test that task remove entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "task" in tools
        assert callable(tools["task"].fn)

    def test_assumption_add_registered(self, mcp_server):
        """Test that assumption add entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_assumption_list_registered(self, mcp_server):
        """Test that assumption list entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_revision_add_registered(self, mcp_server):
        """Test that revision add entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_spec_update_frontmatter_registered(self, mcp_server):
        """Test that spec update frontmatter entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_phase_remove_registered(self, mcp_server):
        """Test that phase remove entrypoint is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert callable(tools["authoring"].fn)

    def test_all_authoring_tools_count(self, mcp_server):
        """Test that authoring surface is registered."""
        tools = mcp_server._tool_manager._tools
        assert "authoring" in tools
        assert "task" in tools


# =============================================================================
# Response Envelope Compliance Tests
# =============================================================================


class TestResponseEnvelopeCompliance:
    """Test that authoring tools return response-v2 compliant envelopes."""

    def _validate_response_envelope(self, response):
        """Validate response follows response-v2 contract."""
        assert isinstance(response, dict), "Response must be a dict"
        assert "success" in response, "Response must have 'success' key"
        assert "data" in response, "Response must have 'data' key"
        assert "error" in response, "Response must have 'error' key"
        assert "meta" in response, "Response must have 'meta' key"
        assert isinstance(response["success"], bool), "success must be boolean"
        assert isinstance(response["data"], dict), "data must be dict"
        assert isinstance(response["meta"], dict), "meta must be dict"
        assert response["meta"].get("version") == "response-v2", (
            "meta.version must be 'response-v2'"
        )

        if response["success"]:
            assert response["error"] is None, "error must be null on success"
        else:
            assert response["error"] is not None, "error must be set on failure"
            assert isinstance(response["error"], str), "error must be string"

    def test_spec_template_list_envelope(self, mcp_server):
        """Test spec-template list action returns valid envelope."""
        tools = mcp_server._tool_manager._tools
        result = _call_tool(tools, "spec-template", action="list")
        self._validate_response_envelope(result)

    def test_assumption_list_envelope(self, mcp_server):
        """Test assumption-list returns valid envelope."""
        tools = mcp_server._tool_manager._tools
        result = _call_tool(tools, "assumption-list", spec_id="authoring-test-spec-001")
        self._validate_response_envelope(result)

    def test_validation_error_envelope(self, mcp_server):
        """Test validation errors return proper envelope with error details."""
        tools = mcp_server._tool_manager._tools
        # Missing required parameter
        with patch(
            "foundry_mcp.tools.unified.common.get_server_role",
            return_value="maintainer",
        ):
            result = _call_tool(
                tools, "task-add", spec_id="", parent="phase-1", title="Test"
            )
        self._validate_response_envelope(result)
        assert result["success"] is False
        assert "spec_id" in result["error"].lower()
        assert "error_code" in result["data"]
        assert "error_type" in result["data"]
        assert "remediation" in result["data"]

    def test_revision_add_validation(self, mcp_server):
        """Test that revision-add validates inputs."""
        tools = mcp_server._tool_manager._tools

        # Missing version
        with patch(
            "foundry_mcp.tools.unified.common.get_server_role",
            return_value="maintainer",
        ):
            result = _call_tool(
                tools,
                "revision-add",
                spec_id="authoring-test-spec-001",
                version="",
                changes="Test changes",
            )

        assert result["success"] is False
        assert "version" in result["error"].lower()

    def test_frontmatter_update_validation(self, mcp_server):
        """Test that frontmatter update validates inputs."""
        tools = mcp_server._tool_manager._tools

        # Missing key
        result = _call_tool(
            tools,
            "spec-update-frontmatter",
            spec_id="authoring-test-spec-001",
            key="",
            value="test",
        )

        assert result["success"] is False
        assert "key" in result["error"].lower()


# =============================================================================
# Template Operations Tests
# =============================================================================


class TestTemplateOperations:
    """Test spec-template tool operations."""

    def test_template_list_action(self, mcp_server):
        """Test template list action."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "spec-template", action="list")

        # CLI may not be available; verify we get a valid response envelope
        assert "success" in result
        assert "data" in result
        # If successful, should have action and templates data
        if result["success"]:
            assert result["data"]["action"] == "list"
            assert "templates" in result["data"] or "total_count" in result["data"]

    def test_template_show_requires_name(self, mcp_server):
        """Test template show action requires template_name."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "spec-template", action="show")

        assert result["success"] is False
        assert "template_name" in result["error"].lower()

    def test_template_apply_requires_name(self, mcp_server):
        """Test template apply action requires template_name."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "spec-template", action="apply")

        assert result["success"] is False
        assert "template_name" in result["error"].lower()

    def test_template_invalid_action(self, mcp_server):
        """Test template with invalid action."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "spec-template", action="invalid_action")

        assert result["success"] is False
        assert "template_action" in result["error"].lower()


# =============================================================================
# Spec Creation Tests
# =============================================================================


class TestSpecCreation:
    """Test spec-create tool operations."""

    def test_spec_create_validates_template(self, mcp_server):
        """Test spec-create validates template parameter."""
        tools = mcp_server._tool_manager._tools

        # Only 'empty' template is valid
        result = _call_tool(
            tools,
            "spec-create",
            name="test-spec",
            template="invalid_template",
        )

        assert result["success"] is False
        assert "INVALID_FORMAT" in str(result["data"].get("error_code", ""))

    def test_spec_create_validates_category(self, mcp_server):
        """Test spec-create validates category parameter."""
        tools = mcp_server._tool_manager._tools

        # Valid categories are: investigation, implementation, refactoring, decision, research
        result = _call_tool(
            tools,
            "spec-create",
            name="test-spec",
            category="invalid_category",
        )

        assert result["success"] is False
        assert "category" in result["error"].lower()

    def test_spec_create_valid_templates_accepted(self, mcp_server):
        """Test spec-create accepts the valid 'empty' template."""
        tools = mcp_server._tool_manager._tools

        # Only 'empty' template is valid
        result = _call_tool(
            tools,
            "spec-create",
            name="test-empty",
            template="empty",
        )
        # May fail due to CLI not being available, but should not fail validation
        if result["success"] is False:
            assert "VALIDATION_ERROR" not in str(
                result["data"].get("error_code", "")
            ), "Template 'empty' should be valid"

    def test_spec_create_rejects_deprecated_templates(self, mcp_server):
        """Test spec-create rejects deprecated templates."""
        tools = mcp_server._tool_manager._tools

        deprecated_templates = ["simple", "medium", "complex", "security"]
        for template in deprecated_templates:
            result = _call_tool(
                tools,
                "spec-create",
                name=f"test-{template}",
                template=template,
            )
            assert result["success"] is False, f"Template '{template}' should be rejected"

    def test_spec_create_valid_categories_accepted(self, mcp_server):
        """Test spec-create accepts all valid categories."""
        tools = mcp_server._tool_manager._tools

        valid_categories = [
            "investigation",
            "implementation",
            "refactoring",
            "decision",
            "research",
        ]
        for category in valid_categories:
            result = _call_tool(
                tools,
                "spec-create",
                name=f"test-{category}",
                category=category,
            )
            # May fail due to CLI not being available, but should not fail validation
            if result["success"] is False:
                assert category.lower() not in result["error"].lower(), (
                    f"Category '{category}' should be valid"
                )


# =============================================================================
# Task Operations Tests
# =============================================================================


class TestTaskOperations:
    """Test task-add and task-remove operations."""

    def test_task_add_validates_spec_id(self, mcp_server):
        """Test task-add validates spec_id."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-add",
            spec_id="",
            parent="phase-1",
            title="Test task",
        )

        assert result["success"] is False
        assert "spec_id" in result["error"].lower()

    def test_task_add_validates_parent(self, mcp_server):
        """Test task-add validates parent."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-add",
            spec_id="test-spec",
            parent="",
            title="Test task",
        )

        assert result["success"] is False
        assert "parent" in result["error"].lower()

    def test_task_add_validates_title(self, mcp_server):
        """Test task-add validates title."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-add",
            spec_id="test-spec",
            parent="phase-1",
            title="",
        )

        assert result["success"] is False
        assert "title" in result["error"].lower()

    def test_task_add_validates_task_type(self, mcp_server):
        """Test task-add validates task_type."""
        tools = mcp_server._tool_manager._tools

        # Valid types are: task, subtask, verify
        result = _call_tool(
            tools,
            "task-add",
            spec_id="test-spec",
            parent="phase-1",
            title="Test",
            task_type="invalid_type",
        )

        assert result["success"] is False
        assert "task_type" in result["error"].lower()

    def test_task_add_accepts_valid_types(self, mcp_server):
        """Test task-add accepts all valid task types."""
        tools = mcp_server._tool_manager._tools

        valid_types = ["task", "subtask", "verify"]
        for task_type in valid_types:
            result = _call_tool(
                tools,
                "task-add",
                spec_id="authoring-test-spec-001",
                parent="phase-1",
                title=f"Test {task_type}",
                task_type=task_type,
            )
            # May fail due to CLI, but should not fail type validation
            if result["success"] is False:
                assert task_type.lower() not in result["error"].lower(), (
                    f"Task type '{task_type}' should be valid"
                )

    def test_task_remove_validates_spec_id(self, mcp_server):
        """Test task-remove validates spec_id."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-remove",
            spec_id="",
            task_id="task-1-1",
        )

        assert result["success"] is False
        assert "spec_id" in result["error"].lower()

    def test_task_remove_validates_task_id(self, mcp_server):
        """Test task-remove validates task_id."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-remove",
            spec_id="test-spec",
            task_id="",
        )

        assert result["success"] is False
        assert "task_id" in result["error"].lower()

    def test_task_add_dry_run_option(self, mcp_server):
        """Test task-add supports dry_run option."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-add",
            spec_id="authoring-test-spec-001",
            parent="phase-1",
            title="Dry run task",
            dry_run=True,
        )

        # Should either succeed with dry_run flag set or fail gracefully
        if result["success"]:
            assert result["data"].get("dry_run") is True

    def test_task_remove_dry_run_option(self, mcp_server):
        """Test task-remove supports dry_run option."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-remove",
            spec_id="authoring-test-spec-001",
            task_id="task-1-2",
            dry_run=True,
        )

        if result["success"]:
            assert result["data"].get("dry_run") is True


class TestPhaseOperations:
    """Test phase-add and phase-remove operations."""

    def test_phase_add_validates_spec_id(self, mcp_server):
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "phase-add", spec_id="", title="Phase")

        assert result["success"] is False
        assert "spec_id" in result["error"].lower()

    def test_phase_add_validates_title(self, mcp_server):
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "phase-add", spec_id="test-spec", title="")

        assert result["success"] is False
        assert "title" in result["error"].lower()

    def test_phase_add_validates_hours(self, mcp_server):
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "phase-add",
            spec_id="test-spec",
            title="Phase",
            estimated_hours=-2,
        )

        assert result["success"] is False
        assert "estimated_hours" in result["error"].lower()

    def test_phase_add_dry_run_option(self, mcp_server):
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "phase-add",
            spec_id="authoring-test-spec-001",
            title="Dry run phase",
            dry_run=True,
        )

        if result["success"]:
            assert result["data"].get("dry_run") is True

    def test_phase_remove_validates_spec_id(self, mcp_server):
        """Test phase-remove validates spec_id."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "phase-remove", spec_id="", phase_id="phase-1")

        assert result["success"] is False
        assert "spec_id" in result["error"].lower()

    def test_phase_remove_validates_phase_id(self, mcp_server):
        """Test phase-remove validates phase_id."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(tools, "phase-remove", spec_id="test-spec", phase_id="")

        assert result["success"] is False
        assert "phase_id" in result["error"].lower()

    def test_phase_remove_dry_run_option(self, mcp_server):
        """Test phase-remove supports dry_run option."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "phase-remove",
            spec_id="authoring-test-spec-001",
            phase_id="phase-1",
            dry_run=True,
        )

        # Should either succeed with dry_run flag set or fail gracefully
        if result["success"]:
            assert result["data"].get("dry_run") is True

    def test_phase_remove_force_option(self, mcp_server):
        """Test phase-remove accepts force option."""
        tools = mcp_server._tool_manager._tools

        # This may fail because phase has tasks, but shouldn't fail validation
        result = _call_tool(
            tools,
            "phase-remove",
            spec_id="authoring-test-spec-001",
            phase_id="phase-1",
            force=True,
        )

        # Should return valid envelope regardless
        assert "success" in result
        assert "data" in result
        assert "meta" in result

    def test_task_remove_dry_run_option(self, mcp_server):
        """Test task-remove supports dry_run option."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-remove",
            spec_id="authoring-test-spec-001",
            task_id="task-1-1",
            dry_run=True,
        )

        # Should either succeed with dry_run flag set or fail gracefully
        if result["success"]:
            assert result["data"].get("dry_run") is True


# =============================================================================
# End-to-End Workflow Tests
# =============================================================================


class TestAuthoringWorkflows:
    """Test end-to-end authoring workflows."""

    def test_list_then_add_assumption_workflow(self, mcp_server):
        """Test workflow: list assumptions, then add new one."""
        tools = mcp_server._tool_manager._tools

        # Step 1: List existing assumptions
        list_result = _call_tool(
            tools, "assumption-list", spec_id="authoring-test-spec-001"
        )

        # Step 2: Add a new assumption
        add_result = _call_tool(
            tools,
            "assumption-add",
            spec_id="authoring-test-spec-001",
            text="New integration test assumption",
            assumption_type="constraint",
        )

        # Both operations should return valid envelopes
        assert "success" in list_result
        assert "success" in add_result

    def test_template_list_then_show_workflow(self, mcp_server):
        """Test workflow: list templates, then show one."""
        tools = mcp_server._tool_manager._tools

        # Step 1: List templates
        list_result = _call_tool(tools, "spec-template", action="list")
        # CLI may not be available; verify we get a valid envelope
        assert "success" in list_result
        assert "data" in list_result

        # Step 2: If templates exist and successful, try to show one
        if list_result["success"] and list_result["data"].get("templates"):
            template_name = list_result["data"]["templates"][0].get("name", "empty")
            show_result = _call_tool(
                tools,
                "spec-template",
                action="show",
                template_name=template_name,
            )
            assert "success" in show_result

    def test_validation_before_creation_workflow(self, mcp_server):
        """Test workflow: validate inputs before creating resources."""
        tools = mcp_server._tool_manager._tools

        # Step 1: Try invalid spec creation (should fail validation)
        invalid_result = _call_tool(
            tools,
            "spec-create",
            name="test",
            template="not_a_real_template",
        )
        assert invalid_result["success"] is False
        assert invalid_result["data"].get("error_code") == "INVALID_FORMAT"

        # Step 2: Try valid parameters (may fail due to CLI, but should pass validation)
        valid_result = _call_tool(
            tools,
            "spec-create",
            name="test",
            template="empty",
        )
        # Should not fail validation
        if valid_result["success"] is False:
            assert valid_result["data"].get("error_code") != "VALIDATION_ERROR"


# =============================================================================
# Error Handling Tests
# =============================================================================


class TestErrorHandling:
    """Test error handling in authoring tools."""

    def test_circuit_breaker_error_response(self, mcp_server):
        """Test that circuit breaker errors are properly handled."""
        # This test verifies the error response format when circuit breaker is open
        # Since we can't easily trigger the circuit breaker in integration tests,
        # we test that the error codes are defined correctly
        tools = mcp_server._tool_manager._tools

        # Make a request that will likely fail
        result = _call_tool(tools, "spec-create", name="test")

        # Should return valid envelope regardless of success
        assert "success" in result
        assert "data" in result
        assert "error" in result
        assert "meta" in result

    def test_timeout_error_includes_remediation(self, mcp_server):
        """Test that timeout errors include remediation guidance."""
        tools = mcp_server._tool_manager._tools

        # Trigger a request that might timeout
        result = _call_tool(
            tools,
            "task-add",
            spec_id="authoring-test-spec-001",
            parent="phase-1",
            title="Test task",
        )

        # If it failed with timeout, check remediation
        if result["success"] is False and "TIMEOUT" in str(
            result["data"].get("error_code", "")
        ):
            assert "remediation" in result["data"]

    def test_not_found_errors_include_remediation(self, mcp_server):
        """Test that not found errors include remediation guidance."""
        tools = mcp_server._tool_manager._tools

        result = _call_tool(
            tools,
            "task-add",
            spec_id="nonexistent-spec-999",
            parent="phase-1",
            title="Test task",
        )

        if result["success"] is False:
            # Should include remediation for not found errors
            if (
                "NOT_FOUND" in str(result["data"].get("error_code", ""))
                or "not found" in result["error"].lower()
            ):
                assert "remediation" in result["data"]
