"""Tests for the send_to_ariba skill."""

from __future__ import annotations

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

import pytest

# Add project root to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))

from core.skills import SkillLoader, to_openai_tool

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def skills_dir() -> Path:
    """Return the skills directory path."""
    return Path(__file__).parent.parent / "skills"


@pytest.fixture
def skill_loader(skills_dir: Path) -> SkillLoader:
    """Create a SkillLoader instance."""
    return SkillLoader(skills_dir)


@pytest.fixture
def sample_line_items() -> str:
    """Sample line items JSON for testing."""
    return json.dumps(
        [
            {
                "description": "Test item 1",
                "part_number": "PART-001",
                "unit_price": 10000,
                "quantity": 1,
                "item_class": "C01",
                "supplier": "0076112",
                "supplier_location": "0076112A01",
            }
        ]
    )


@pytest.fixture
def ariba_env_vars(monkeypatch):
    """Set up Ariba environment variables for testing."""
    monkeypatch.setenv("ARIBA_URL", "https://test.ariba.com/api")
    monkeypatch.setenv("ARIBA_USERNAME", "test_user")
    monkeypatch.setenv("ARIBA_PASSWORD", "test_password")
    monkeypatch.setenv("ARIBA_VREALM", "vrealm_test")
    monkeypatch.setenv("ARIBA_PASSWORD_ADAPTER", "TestAdapter")


# ---------------------------------------------------------------------------
# Skill Loading Tests
# ---------------------------------------------------------------------------


class TestSendToAribaSkillLoading:
    """Tests for loading the send_to_ariba skill."""

    def test_skill_json_exists(self, skills_dir: Path) -> None:
        """Verify skill.json file exists."""
        skill_json = skills_dir / "send_to_ariba" / "skill.json"
        assert skill_json.exists(), "skill.json not found"

    def test_handler_py_exists(self, skills_dir: Path) -> None:
        """Verify handler.py file exists."""
        handler_py = skills_dir / "send_to_ariba" / "handler.py"
        assert handler_py.exists(), "handler.py not found"

    def test_skill_json_valid(self, skills_dir: Path) -> None:
        """Verify skill.json is valid JSON."""
        skill_json = skills_dir / "send_to_ariba" / "skill.json"
        with open(skill_json) as f:
            config = json.load(f)

        assert config["name"] == "send_to_ariba"
        assert "description" in config
        assert "parameters" in config

    def test_skill_loads_successfully(self, skill_loader: SkillLoader) -> None:
        """Verify the skill loads without errors."""
        skills = skill_loader.load_all()
        assert "send_to_ariba" in skills, "send_to_ariba skill not loaded"

    def test_skill_has_handler(self, skill_loader: SkillLoader) -> None:
        """Verify the skill has a handler function."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]
        assert skill.has_handler, "send_to_ariba should have a handler"

    def test_skill_parameters_loaded(self, skill_loader: SkillLoader) -> None:
        """Verify all expected parameters are loaded."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        param_names = {p.name for p in skill.parameters}
        expected_params = {
            "operation",
            "pr_number",
            "title",
            "requester",
            "preparer",
            "deliver_to",
            "need_by",
            "line_items",
            "comment",
            "attachment_path",
            "attachment_filename",
        }
        assert expected_params.issubset(param_names), f"Missing parameters: {expected_params - param_names}"

    def test_required_parameters(self, skill_loader: SkillLoader) -> None:
        """Verify required parameters are marked correctly."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        required_params = {p.name for p in skill.parameters if p.required}
        expected_required = {"operation", "title", "requester", "preparer", "deliver_to", "need_by", "line_items"}
        assert required_params == expected_required, (
            f"Required params mismatch: {required_params} vs {expected_required}"
        )

    def test_pr_number_is_optional(self, skill_loader: SkillLoader) -> None:
        """Verify pr_number is optional (for Create operations)."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        pr_param = next((p for p in skill.parameters if p.name == "pr_number"), None)
        assert pr_param is not None, "pr_number parameter not found"
        assert pr_param.required is False, "pr_number should be optional"


# ---------------------------------------------------------------------------
# OpenAI Tool Conversion Tests
# ---------------------------------------------------------------------------


class TestSendToAribaOpenAITool:
    """Tests for converting send_to_ariba to OpenAI tool format."""

    def test_converts_to_openai_tool(self, skill_loader: SkillLoader) -> None:
        """Verify skill converts to valid OpenAI tool format."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        tool = to_openai_tool(skill)

        assert tool["type"] == "function"
        assert tool["function"]["name"] == "send_to_ariba"
        assert "description" in tool["function"]
        assert "parameters" in tool["function"]

    def test_openai_tool_has_required_fields(self, skill_loader: SkillLoader) -> None:
        """Verify OpenAI tool has correct required fields."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        tool = to_openai_tool(skill)
        params = tool["function"]["parameters"]

        assert params["type"] == "object"
        assert "properties" in params
        assert "required" in params

        # pr_number should NOT be in required
        assert "pr_number" not in params["required"], "pr_number should not be required"

        # These should be required
        for param in ["operation", "title", "requester", "preparer", "deliver_to", "need_by", "line_items"]:
            assert param in params["required"], f"{param} should be required"

    def test_openai_tool_parameter_types(self, skill_loader: SkillLoader) -> None:
        """Verify parameter types in OpenAI tool format."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        tool = to_openai_tool(skill)
        props = tool["function"]["parameters"]["properties"]

        assert props["operation"]["type"] == "string"
        assert props["line_items"]["type"] == "string"


# ---------------------------------------------------------------------------
# Handler Function Tests
# ---------------------------------------------------------------------------


class TestSendToAribaHandler:
    """Tests for the send_to_ariba handler function."""

    def test_handler_import(self) -> None:
        """Verify handler can be imported."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        assert hasattr(module, "handle"), "Handler module should have 'handle' function"
        assert callable(module.handle), "'handle' should be callable"

    def test_handler_rejects_invalid_operation(self, sample_line_items: str) -> None:
        """Verify handler rejects invalid operation."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        result = module.handle(
            operation="InvalidOp",
            title="Test",
            requester="USER1",
            preparer="USER1",
            deliver_to="Location",
            need_by="2025-07-22T10:00:00Z",
            line_items=sample_line_items,
        )

        data = json.loads(result)
        assert data["ok"] is False
        assert "Operation must be" in data["error"]

    def test_handler_requires_pr_number_for_update(self, sample_line_items: str) -> None:
        """Verify handler requires pr_number for Update operation."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        result = module.handle(
            operation="Update",
            title="Test",
            requester="USER1",
            preparer="USER1",
            deliver_to="Location",
            need_by="2025-07-22T10:00:00Z",
            line_items=sample_line_items,
            pr_number=None,  # Missing for Update
        )

        data = json.loads(result)
        assert data["ok"] is False
        assert "pr_number is required" in data["error"]

    def test_handler_accepts_create_without_pr_number(self, sample_line_items: str, ariba_env_vars) -> None:
        """Verify handler accepts Create operation without pr_number."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        with patch.object(module.requests, "post") as mock_post:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.text = "<success>OK</success>"
            mock_post.return_value = mock_response

            result = module.handle(
                operation="Create",
                title="Test PR",
                requester="USER1",
                preparer="USER1",
                deliver_to="Location",
                need_by="2025-07-22T10:00:00Z",
                line_items=sample_line_items,
                # pr_number not provided - should be OK for Create
            )

            data = json.loads(result)
            assert data["ok"] is True, f"Expected success but got: {data}"

    def test_handler_rejects_empty_line_items(self) -> None:
        """Verify handler rejects empty line items."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        result = module.handle(
            operation="Create",
            title="Test",
            requester="USER1",
            preparer="USER1",
            deliver_to="Location",
            need_by="2025-07-22T10:00:00Z",
            line_items="[]",  # Empty array
        )

        data = json.loads(result)
        assert data["ok"] is False
        assert "At least one line item" in data["error"]

    def test_handler_rejects_invalid_json_line_items(self) -> None:
        """Verify handler rejects invalid JSON in line_items."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        result = module.handle(
            operation="Create",
            title="Test",
            requester="USER1",
            preparer="USER1",
            deliver_to="Location",
            need_by="2025-07-22T10:00:00Z",
            line_items="not valid json",
        )

        data = json.loads(result)
        assert data["ok"] is False
        assert "Invalid line_items JSON" in data["error"]

    def test_handler_missing_ariba_config(self, sample_line_items: str, monkeypatch) -> None:
        """Verify handler reports missing Ariba configuration."""
        from importlib.util import module_from_spec, spec_from_file_location

        # Clear Ariba env vars
        monkeypatch.delenv("ARIBA_URL", raising=False)
        monkeypatch.delenv("ARIBA_USERNAME", raising=False)
        monkeypatch.delenv("ARIBA_PASSWORD", raising=False)

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler_noconfig", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        # Force reload of config by patching _get_ariba_config
        original_get_config = module._get_ariba_config
        module._get_ariba_config = lambda: {
            "url": None,
            "username": None,
            "password": None,
            "vrealm": "test",
            "password_adapter": "test",
        }

        try:
            result = module.handle(
                operation="Create",
                title="Test",
                requester="USER1",
                preparer="USER1",
                deliver_to="Location",
                need_by="2025-07-22T10:00:00Z",
                line_items=sample_line_items,
            )

            data = json.loads(result)
            assert data["ok"] is False
            assert "Missing Ariba configuration" in data["error"]
        finally:
            module._get_ariba_config = original_get_config


# ---------------------------------------------------------------------------
# Integration Tests with SkillExecutor
# ---------------------------------------------------------------------------


class TestSendToAribaExecution:
    """Integration tests for executing the send_to_ariba skill."""

    def test_execute_handler_directly(self, skill_loader: SkillLoader, sample_line_items: str, ariba_env_vars) -> None:
        """Verify skill handler can be executed directly."""
        skills = skill_loader.load_all()
        skill = skills["send_to_ariba"]

        assert skill.handler is not None, "Skill should have a handler"

        # Mock the requests.post call
        with patch("requests.post") as mock_post:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.text = "<response>Success</response>"
            mock_post.return_value = mock_response

            result = skill.handler(
                operation="Create",
                title="Test PR from Handler",
                requester="TENVUSER0170",
                preparer="TENVUSER0170",
                deliver_to="Test Location",
                need_by="2025-07-22T10:00:00Z",
                line_items=sample_line_items,
            )

            data = json.loads(result)
            assert data["ok"] is True, f"Execution failed: {data}"
            assert data["operation"] == "Create"
            assert data["line_item_count"] == 1


# ---------------------------------------------------------------------------
# SOAP/XML Building Tests
# ---------------------------------------------------------------------------


class TestSoapXmlBuilding:
    """Tests for SOAP XML building functions."""

    def test_build_line_item_xml(self) -> None:
        """Verify line item XML is built correctly."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        item = {
            "description": "テストアイテム",
            "part_number": "PART-001",
            "unit_price": 15000,
            "quantity": 2,
            "item_class": "C01",
            "supplier": "SUP001",
            "supplier_location": "SUP001A01",
        }

        xml = module._build_line_item_xml(
            item=item,
            line_number=1,
            operation="Create",
            deliver_to="テスト場所",
            need_by="2025-08-01T10:00:00Z",
        )

        assert "PART-001" in xml
        assert "15000" in xml
        assert "テストアイテム" in xml
        assert "SUP001" in xml

    def test_build_soap_envelope(self, ariba_env_vars) -> None:
        """Verify SOAP envelope is built correctly."""
        from importlib.util import module_from_spec, spec_from_file_location

        handler_path = Path(__file__).parent.parent / "skills" / "send_to_ariba" / "handler.py"
        spec = spec_from_file_location("send_to_ariba_handler", handler_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)

        config = module._get_ariba_config()

        line_items = [
            {
                "description": "Item 1",
                "part_number": "P1",
                "unit_price": 1000,
                "quantity": 1,
                "supplier": "S1",
                "supplier_location": "S1A",
            },
        ]

        envelope = module._build_soap_envelope(
            config=config,
            operation="Create",
            pr_number="",
            title="Test Requisition",
            requester="USER1",
            preparer="USER1",
            deliver_to="Location",
            need_by="2025-07-22T10:00:00Z",
            line_items=line_items,
            comment="Test comment",
        )

        assert "soapenv:Envelope" in envelope
        assert "RequisitionImportPullRequest" in envelope
        assert "Test Requisition" in envelope
        assert "Item 1" in envelope
