"""Tests for SHIP CrewAI tools."""

import json
import pytest
from unittest.mock import patch, MagicMock

from ship_crewai import SHIPScoreTool, SHIPDetectTool, SHIPHealthTool
from ship_crewai.tools import ScoreInput, DetectInput


class TestSHIPScoreTool:
    """Tests for SHIPScoreTool."""

    def test_tool_metadata(self):
        """Test tool has correct name and description."""
        tool = SHIPScoreTool()
        assert tool.name == "SHIP Score"
        assert "reliability" in tool.description.lower()
        assert "score" in tool.description.lower()

    def test_args_schema(self):
        """Test input schema is properly set."""
        tool = SHIPScoreTool()
        assert tool.args_schema == ScoreInput

    def test_default_config(self):
        """Test default client configuration."""
        tool = SHIPScoreTool()
        assert "ship-protocol.dhruvaapi.workers.dev" in tool.base_url
        assert tool.timeout == 30.0

    def test_custom_config(self):
        """Test custom client configuration."""
        tool = SHIPScoreTool(
            base_url="https://custom.api.dev",
            api_key="test-key-123",
            timeout=60.0,
        )
        assert tool.base_url == "https://custom.api.dev"
        assert tool.api_key == "test-key-123"
        assert tool.timeout == 60.0

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_success(self, mock_client_class, mock_score_response):
        """Test successful score retrieval."""
        mock_client = MagicMock()
        mock_client.score.return_value = mock_score_response
        mock_client_class.return_value = mock_client

        tool = SHIPScoreTool()
        result = tool._run(repo="facebook/react")
        parsed = json.loads(result)

        assert parsed["repo"] == "facebook/react"
        assert parsed["ship_score"] == 85
        assert parsed["grade"] == "A"
        assert "breakdown" in parsed
        assert "recommendation" in parsed
        mock_client.score.assert_called_once_with("facebook/react")

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_with_breakdown(self, mock_client_class, mock_score_response):
        """Test score response includes breakdown details."""
        mock_client = MagicMock()
        mock_client.score.return_value = mock_score_response
        mock_client_class.return_value = mock_client

        tool = SHIPScoreTool()
        result = tool._run(repo="owner/repo")
        parsed = json.loads(result)

        assert parsed["breakdown"]["confidence"] == 88
        assert parsed["breakdown"]["focus"] == 82

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_error_returns_fallback(self, mock_client_class):
        """Test error returns fallback response instead of raising."""
        mock_client = MagicMock()
        mock_client.score.side_effect = Exception("Connection refused")
        mock_client_class.return_value = mock_client

        tool = SHIPScoreTool()
        result = tool._run(repo="owner/repo")
        parsed = json.loads(result)

        # Should not raise -- antifragile
        assert parsed["fallback"] is True
        assert parsed["ship_score"] is None
        assert parsed["grade"] == "UNKNOWN"
        assert "Connection refused" in parsed["error"]

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_timeout_returns_fallback(self, mock_client_class):
        """Test timeout returns fallback response."""
        import httpx

        mock_client = MagicMock()
        mock_client.score.side_effect = httpx.TimeoutException("Request timed out")
        mock_client_class.return_value = mock_client

        tool = SHIPScoreTool()
        result = tool._run(repo="owner/repo")
        parsed = json.loads(result)

        assert parsed["fallback"] is True
        assert "timed out" in parsed["error"].lower()


class TestSHIPDetectTool:
    """Tests for SHIPDetectTool."""

    def test_tool_metadata(self):
        """Test tool has correct name and description."""
        tool = SHIPDetectTool()
        assert tool.name == "SHIP Detect"
        assert "detect" in tool.description.lower()
        assert "ai" in tool.description.lower()

    def test_args_schema(self):
        """Test input schema is properly set."""
        tool = SHIPDetectTool()
        assert tool.args_schema == DetectInput

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_success(self, mock_client_class, mock_detect_response, sample_python_code):
        """Test successful AI detection."""
        mock_client = MagicMock()
        mock_client.detect.return_value = mock_detect_response
        mock_client_class.return_value = mock_client

        tool = SHIPDetectTool()
        result = tool._run(code=sample_python_code, language="python")
        parsed = json.loads(result)

        assert parsed["ai_probability"] == 0.87
        assert parsed["is_ai_generated"] is True
        assert parsed["confidence"] == 0.92
        assert parsed["language"] == "python"
        assert parsed["features_analyzed"] == 174
        assert "interpretation" in parsed
        mock_client.detect.assert_called_once_with(sample_python_code, "python")

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_with_typescript(self, mock_client_class, sample_typescript_code):
        """Test detection with TypeScript code."""
        mock_client = MagicMock()
        mock_client.detect.return_value = {
            "ai_probability": 0.45,
            "is_ai_generated": False,
            "confidence": 0.78,
            "features_analyzed": 174,
        }
        mock_client_class.return_value = mock_client

        tool = SHIPDetectTool()
        result = tool._run(code=sample_typescript_code, language="typescript")
        parsed = json.loads(result)

        assert parsed["ai_probability"] == 0.45
        assert parsed["is_ai_generated"] is False
        assert parsed["language"] == "typescript"
        mock_client.detect.assert_called_once_with(sample_typescript_code, "typescript")

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_default_language(self, mock_client_class, mock_detect_response):
        """Test default language is python."""
        mock_client = MagicMock()
        mock_client.detect.return_value = mock_detect_response
        mock_client_class.return_value = mock_client

        tool = SHIPDetectTool()
        tool._run(code="print('hello')")

        mock_client.detect.assert_called_once_with("print('hello')", "python")

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_error_returns_fallback(self, mock_client_class):
        """Test error returns fallback response."""
        mock_client = MagicMock()
        mock_client.detect.side_effect = Exception("Service unavailable")
        mock_client_class.return_value = mock_client

        tool = SHIPDetectTool()
        result = tool._run(code="def foo(): pass")
        parsed = json.loads(result)

        assert parsed["fallback"] is True
        assert parsed["ai_probability"] is None
        assert parsed["is_ai_generated"] is None
        assert "Service unavailable" in parsed["error"]

    @patch("ship_crewai.tools.SHIPClient")
    def test_interpretation_high_probability(self, mock_client_class):
        """Test interpretation for high AI probability."""
        mock_client = MagicMock()
        mock_client.detect.return_value = {
            "ai_probability": 0.95,
            "is_ai_generated": True,
            "confidence": 0.98,
            "features_analyzed": 174,
        }
        mock_client_class.return_value = mock_client

        tool = SHIPDetectTool()
        result = tool._run(code="x = 1")
        parsed = json.loads(result)

        assert "very likely ai-generated" in parsed["interpretation"].lower()

    @patch("ship_crewai.tools.SHIPClient")
    def test_interpretation_low_probability(self, mock_client_class):
        """Test interpretation for low AI probability."""
        mock_client = MagicMock()
        mock_client.detect.return_value = {
            "ai_probability": 0.15,
            "is_ai_generated": False,
            "confidence": 0.88,
            "features_analyzed": 174,
        }
        mock_client_class.return_value = mock_client

        tool = SHIPDetectTool()
        result = tool._run(code="x = 1")
        parsed = json.loads(result)

        assert "very unlikely ai-generated" in parsed["interpretation"].lower()


class TestSHIPHealthTool:
    """Tests for SHIPHealthTool."""

    def test_tool_metadata(self):
        """Test tool has correct name and description."""
        tool = SHIPHealthTool()
        assert tool.name == "SHIP Health"
        assert "health" in tool.description.lower()

    def test_no_args_schema(self):
        """Test health tool has no required input schema."""
        tool = SHIPHealthTool()
        # Health tool should work without any input
        # BaseTool default args_schema is fine

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_healthy(self, mock_client_class, mock_health_response):
        """Test health check when API is healthy."""
        mock_client = MagicMock()
        mock_client.health.return_value = mock_health_response
        mock_client_class.return_value = mock_client

        tool = SHIPHealthTool()
        result = tool._run()
        parsed = json.loads(result)

        assert parsed["status"] == "healthy"
        assert parsed["version"] == "2.0.0"
        assert "available" in parsed["message"].lower()

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_unhealthy(self, mock_client_class):
        """Test health check when API is unavailable."""
        mock_client = MagicMock()
        mock_client.health.side_effect = Exception("Connection refused")
        mock_client_class.return_value = mock_client

        tool = SHIPHealthTool()
        result = tool._run()
        parsed = json.loads(result)

        assert parsed["status"] == "unhealthy"
        assert "Connection refused" in parsed["error"]
        assert "unavailable" in parsed["message"].lower()

    @patch("ship_crewai.tools.SHIPClient")
    def test_run_with_custom_base_url(self, mock_client_class, mock_health_response):
        """Test health check with custom base URL."""
        mock_client = MagicMock()
        mock_client.health.return_value = mock_health_response
        mock_client_class.return_value = mock_client

        tool = SHIPHealthTool(base_url="https://staging.ship.dev")
        tool._run()

        mock_client_class.assert_called_once_with(
            base_url="https://staging.ship.dev",
            api_key=None,
            timeout=30.0,
        )


class TestInputSchemas:
    """Tests for input validation schemas."""

    def test_score_input_requires_repo(self):
        """Test ScoreInput requires repo field."""
        input_data = ScoreInput(repo="owner/repo")
        assert input_data.repo == "owner/repo"

    def test_score_input_repo_validation(self):
        """Test ScoreInput accepts various repo formats."""
        # Standard format
        assert ScoreInput(repo="facebook/react").repo == "facebook/react"
        # Org with hyphens
        assert ScoreInput(repo="my-org/my-repo").repo == "my-org/my-repo"

    def test_detect_input_defaults(self):
        """Test DetectInput has correct defaults."""
        input_data = DetectInput(code="def hello(): pass")
        assert input_data.code == "def hello(): pass"
        assert input_data.language == "python"

    def test_detect_input_custom_language(self):
        """Test DetectInput accepts custom language."""
        input_data = DetectInput(code="console.log('hi')", language="javascript")
        assert input_data.language == "javascript"


class TestGradeRecommendations:
    """Tests for grade recommendation helper."""

    @patch("ship_crewai.tools.SHIPClient")
    def test_grade_a_plus_recommendation(self, mock_client_class):
        """Test A+ grade recommendation."""
        mock_client = MagicMock()
        mock_client.score.return_value = {"score": 97, "grade": "A+", "breakdown": {}}
        mock_client_class.return_value = mock_client

        tool = SHIPScoreTool()
        result = tool._run(repo="owner/repo")
        parsed = json.loads(result)

        assert "exceptional" in parsed["recommendation"].lower()

    @patch("ship_crewai.tools.SHIPClient")
    def test_grade_f_recommendation(self, mock_client_class):
        """Test F grade recommendation."""
        mock_client = MagicMock()
        mock_client.score.return_value = {"score": 15, "grade": "F", "breakdown": {}}
        mock_client_class.return_value = mock_client

        tool = SHIPScoreTool()
        result = tool._run(repo="owner/repo")
        parsed = json.loads(result)

        assert "unreliable" in parsed["recommendation"].lower()
