"""
Tests for Contract Tool (OpenAPI/JSON Schema)
"""

import pytest
import os
import sys
import yaml

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

from services.router.tool_manager import ToolManager, ToolResult


class TestContractToolLinting:
    """Test OpenAPI linting functionality"""
    
    @pytest.mark.asyncio
    async def test_lint_missing_operation_id(self):
        """Test detection of missing operationId"""
        tool_mgr = ToolManager({})
        
        spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths:
  /users:
    get:
      responses:
        "200":
          description: Success
"""
        
        result = await tool_mgr._contract_tool({
            "action": "lint_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": spec}
            },
            "options": {}
        })
        
        assert result.success is True
        assert len(result.result["lint"]) > 0
        assert any("operationId" in i["message"] for i in result.result["lint"])
    
    @pytest.mark.asyncio
    async def test_lint_good_spec(self):
        """Test that valid spec passes lint"""
        tool_mgr = ToolManager({})
        
        spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths:
  /users:
    get:
      operationId: getUsers
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: array
"""
        
        result = await tool_mgr._contract_tool({
            "action": "lint_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": spec}
            },
            "options": {}
        })
        
        assert result.success is True
        errors = [i for i in result.result["lint"] if i["severity"] == "error"]
        assert len(errors) == 0
    
    @pytest.mark.asyncio
    async def test_lint_no_spec(self):
        """Test error when no spec provided"""
        tool_mgr = ToolManager({})
        
        result = await tool_mgr._contract_tool({
            "action": "lint_openapi",
            "inputs": {},
            "options": {}
        })
        
        assert result.success is False


class TestContractToolDiff:
    """Test OpenAPI diff/breaking changes detection"""
    
    @pytest.mark.asyncio
    async def test_endpoint_removed_breaking(self):
        """Test detection of removed endpoint"""
        tool_mgr = ToolManager({})
        
        base_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths:
  /users:
    get:
      operationId: getUsers
      responses:
        "200":
          description: Success
"""
        
        head_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
"""
        
        result = await tool_mgr._contract_tool({
            "action": "diff_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": base_spec},
                "head": {"source": "text", "value": head_spec}
            },
            "options": {}
        })
        
        assert result.success is True
        assert len(result.result["breaking"]) > 0
        assert any("endpoint_removed" in i["type"] for i in result.result["breaking"])
    
    @pytest.mark.asyncio
    async def test_required_field_added_breaking(self):
        """Test detection of required field added"""
        tool_mgr = ToolManager({})
        
        base_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
components:
  schemas:
    User:
      type: object
      properties:
        name:
          type: string
"""
        
        head_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
components:
  schemas:
    User:
      type: object
      required:
        - email
      properties:
        name:
          type: string
        email:
          type: string
"""
        
        result = await tool_mgr._contract_tool({
            "action": "diff_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": base_spec},
                "head": {"source": "text", "value": head_spec}
            },
            "options": {}
        })
        
        assert result.success is True
        assert len(result.result["breaking"]) > 0
        assert any("required_added" in i["type"] for i in result.result["breaking"])
    
    @pytest.mark.asyncio
    async def test_optional_field_added_nonbreaking(self):
        """Test that optional field addition is non-breaking"""
        tool_mgr = ToolManager({})
        
        base_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
components:
  schemas:
    User:
      type: object
      properties:
        name:
          type: string
"""
        
        head_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
components:
  schemas:
    User:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
"""
        
        result = await tool_mgr._contract_tool({
            "action": "diff_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": base_spec},
                "head": {"source": "text", "value": head_spec}
            },
            "options": {}
        })
        
        assert result.success is True
        # Should have non-breaking, no breaking
        breaking = result.result.get("breaking", [])
        assert len(breaking) == 0
    
    @pytest.mark.asyncio
    async def test_enum_narrowed_breaking(self):
        """Test detection of enum narrowing"""
        tool_mgr = ToolManager({})
        
        base_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
components:
  schemas:
    Status:
      type: string
      enum:
        - pending
        - active
        - completed
"""
        
        head_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
components:
  schemas:
    Status:
      type: string
      enum:
        - pending
        - active
"""
        
        result = await tool_mgr._contract_tool({
            "action": "diff_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": base_spec},
                "head": {"source": "text", "value": head_spec}
            },
            "options": {}
        })
        
        assert result.success is True
        assert len(result.result["breaking"]) > 0
    
    @pytest.mark.asyncio
    async def test_fail_on_breaking(self):
        """Test fail_on_breaking option"""
        tool_mgr = ToolManager({})
        
        base_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths:
  /users:
    get:
      operationId: getUsers
      responses:
        "200":
          description: Success
"""
        
        head_spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
"""
        
        result = await tool_mgr._contract_tool({
            "action": "diff_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": base_spec},
                "head": {"source": "text", "value": head_spec}
            },
            "options": {
                "fail_on_breaking": True
            }
        })
        
        assert result.success is False
        assert result.error is not None


class TestContractToolLimits:
    """Test limit enforcement"""
    
    @pytest.mark.asyncio
    async def test_max_chars_limit(self):
        """Test that max_chars limit is enforced"""
        tool_mgr = ToolManager({})
        
        large_spec = "x" * 900000  # 900KB
        
        result = await tool_mgr._contract_tool({
            "action": "lint_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": large_spec}
            },
            "options": {
                "max_chars": 800000
            }
        })
        
        assert result.success is False
        assert "too large" in result.error.lower()
    
    @pytest.mark.asyncio
    async def test_missing_base_or_head(self):
        """Test error when base or head missing for diff"""
        tool_mgr = ToolManager({})
        
        spec = """
openapi: "3.0.0"
info:
  title: Test API
  version: "1.0.0"
paths: {}
"""
        
        result = await tool_mgr._contract_tool({
            "action": "diff_openapi",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": spec}
            },
            "options": {}
        })
        
        assert result.success is False


class TestContractToolStub:
    """Test client stub generation"""
    
    @pytest.mark.asyncio
    async def test_generate_python_stub(self):
        """Test Python client stub generation"""
        tool_mgr = ToolManager({})
        
        spec = """
openapi: "3.0.0"
info:
  title: User API
  version: "1.0.0"
paths:
  /users:
    get:
      operationId: getUsers
      summary: Get all users
      responses:
        "200":
          description: Success
    post:
      operationId: createUser
      summary: Create user
      parameters:
        - name: data
          in: body
          required: true
      responses:
        "201":
          description: Created
"""
        
        result = await tool_mgr._contract_tool({
            "action": "generate_client_stub",
            "inputs": {
                "format": "openapi_yaml",
                "base": {"source": "text", "value": spec}
            },
            "options": {}
        })
        
        assert result.success is True
        assert "client_stub" in result.result
        assert "def getUsers" in result.result["client_stub"]
        assert "def createUser" in result.result["client_stub"]


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
