"""
Tests for the Ashr Labs SDK client.
"""

import json
import pytest
from unittest.mock import patch, MagicMock
from urllib.error import HTTPError
from io import BytesIO

from ashr_labs import (
    AshrLabsClient,
    AuthenticationError,
    AuthorizationError,
    NotFoundError,
    ValidationError,
)


class TestClientInitialization:
    """Tests for client initialization."""

    def test_valid_api_key(self):
        """Client should accept valid API key format."""
        client = AshrLabsClient(
            api_key="tp_abcdefghijklmnopqrstuvwxyz123456",
            base_url="https://api.example.com"
        )
        assert client.api_key.startswith("tp_")

    def test_invalid_api_key_empty(self):
        """Client should reject empty API key."""
        with pytest.raises(ValueError, match="Invalid API key format"):
            AshrLabsClient(api_key="", base_url="https://api.example.com")

    def test_invalid_api_key_wrong_prefix(self):
        """Client should reject API key without tp_ prefix."""
        with pytest.raises(ValueError, match="Invalid API key format"):
            AshrLabsClient(
                api_key="sk_abcdefghijklmnopqrstuvwxyz123456",
                base_url="https://api.example.com"
            )

    def test_base_url_trailing_slash_removed(self):
        """Base URL trailing slash should be removed."""
        client = AshrLabsClient(
            api_key="tp_test123",
            base_url="https://api.example.com/"
        )
        assert client.base_url == "https://api.example.com"

    def test_custom_timeout(self):
        """Client should accept custom timeout."""
        client = AshrLabsClient(
            api_key="tp_test123",
            base_url="https://api.example.com",
            timeout=60
        )
        assert client.timeout == 60

    def test_default_base_url(self):
        """Client should use default base URL when not provided."""
        client = AshrLabsClient(api_key="tp_test123")
        assert client.base_url == "https://api.ashr.io/testing-platform-api"

    @patch.dict("os.environ", {"ASHR_LABS_API_KEY": "tp_from_env_key"})
    def test_from_env(self):
        """from_env should read API key from environment."""
        client = AshrLabsClient.from_env()
        assert client.api_key == "tp_from_env_key"
        assert client.base_url == "https://api.ashr.io/testing-platform-api"

    @patch.dict("os.environ", {}, clear=True)
    def test_from_env_missing_key(self):
        """from_env should raise RuntimeError when env var not set."""
        with pytest.raises(RuntimeError, match="ASHR_LABS_API_KEY"):
            AshrLabsClient.from_env()


class TestDatasetOperations:
    """Tests for dataset operations."""

    @pytest.fixture
    def client(self):
        return AshrLabsClient(
            api_key="tp_test123",
            base_url="https://api.example.com"
        )

    @patch("ashr_labs.client.urlopen")
    def test_get_dataset_success(self, mock_urlopen, client):
        """get_dataset should return dataset on success."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps({
            "status": "ok",
            "dataset": {
                "id": 42,
                "name": "Test Dataset",
                "tenant": 1
            }
        }).encode()
        mock_response.__enter__ = lambda s: s
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        dataset = client.get_dataset(dataset_id=42)

        assert dataset["id"] == 42
        assert dataset["name"] == "Test Dataset"

    @patch("ashr_labs.client.urlopen")
    def test_list_datasets_success(self, mock_urlopen, client):
        """list_datasets should return datasets list."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps({
            "status": "ok",
            "datasets": [
                {"id": 1, "name": "Dataset 1"},
                {"id": 2, "name": "Dataset 2"}
            ]
        }).encode()
        mock_response.__enter__ = lambda s: s
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        response = client.list_datasets(tenant_id=1)

        assert len(response["datasets"]) == 2


class TestRunOperations:
    """Tests for run operations."""

    @pytest.fixture
    def client(self):
        return AshrLabsClient(
            api_key="tp_test123",
            base_url="https://api.example.com"
        )

    @patch("ashr_labs.client.urlopen")
    def test_create_run_success(self, mock_urlopen, client):
        """create_run should return created run."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps({
            "status": "ok",
            "run": {
                "id": 99,
                "dataset": 42,
                "result": {"score": 0.95}
            }
        }).encode()
        mock_response.__enter__ = lambda s: s
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        run = client.create_run(
            dataset_id=42,
            result={"score": 0.95},
            tenant_id=1,
        )

        assert run["id"] == 99
        assert run["result"]["score"] == 0.95


class TestErrorHandling:
    """Tests for error handling."""

    @pytest.fixture
    def client(self):
        return AshrLabsClient(
            api_key="tp_test123",
            base_url="https://api.example.com"
        )

    @patch("ashr_labs.client.urlopen")
    def test_authentication_error(self, mock_urlopen, client):
        """Should raise AuthenticationError on 401."""
        error = HTTPError(
            url="https://api.example.com",
            code=401,
            msg="Unauthorized",
            hdrs={},
            fp=BytesIO(json.dumps({"message": "Invalid API key"}).encode())
        )
        mock_urlopen.side_effect = error

        with pytest.raises(AuthenticationError):
            client.get_dataset(dataset_id=42)

    @patch("ashr_labs.client.urlopen")
    def test_authorization_error(self, mock_urlopen, client):
        """Should raise AuthorizationError on 403."""
        error = HTTPError(
            url="https://api.example.com",
            code=403,
            msg="Forbidden",
            hdrs={},
            fp=BytesIO(json.dumps({"message": "Access denied"}).encode())
        )
        mock_urlopen.side_effect = error

        with pytest.raises(AuthorizationError):
            client.get_dataset(dataset_id=42)

    @patch("ashr_labs.client.urlopen")
    def test_not_found_error(self, mock_urlopen, client):
        """Should raise NotFoundError on 404."""
        error = HTTPError(
            url="https://api.example.com",
            code=404,
            msg="Not Found",
            hdrs={},
            fp=BytesIO(json.dumps({"message": "Dataset not found"}).encode())
        )
        mock_urlopen.side_effect = error

        with pytest.raises(NotFoundError):
            client.get_dataset(dataset_id=999)

    @patch("ashr_labs.client.urlopen")
    def test_api_level_error(self, mock_urlopen, client):
        """Should handle API-level errors in response body."""
        mock_response = MagicMock()
        mock_response.read.return_value = json.dumps({
            "status": "error",
            "message": "Invalid dataset ID"
        }).encode()
        mock_response.__enter__ = lambda s: s
        mock_response.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_response

        with pytest.raises(ValidationError):
            client.get_dataset(dataset_id=-1)


class TestRepr:
    """Tests for string representation."""

    def test_repr_masks_api_key(self):
        """repr should mask API key for security."""
        client = AshrLabsClient(
            api_key="tp_abcdefghijklmnopqrstuvwxyz123456",
            base_url="https://api.example.com"
        )
        repr_str = repr(client)

        assert "tp_abcde..." in repr_str
        assert "abcdefghijklmnopqrstuvwxyz123456" not in repr_str
