"""Tests for interactive discovery features (__repr__, __dir__, rich representations)."""

from io import StringIO

import pytest

from pinecone import AsyncPinecone, Pinecone
from pinecone.async_client.inference import AsyncInference
from pinecone.client.inference import Inference
from pinecone.models.inference import (
    Embedding,
    EmbedResponse,
    ListModelsResponse,
    ModelInfo,
    RerankResponse,
    RerankResult,
    Usage,
)
from tests.unit.helpers import (
    InteractiveValidationError,
    assert_has_custom_dir,
    assert_has_custom_repr,
    assert_has_repr_pretty,
    assert_repr_hides_secrets,
    validate_client_class,
    validate_response_class,
)


class TestClientRepr:
    """Test __repr__ for client classes."""

    def test_pinecone_repr_no_api_key(self):
        """Verify Pinecone __repr__ doesn't leak API key."""
        pc = Pinecone(api_key="secret-key-12345")
        repr_str = repr(pc)

        assert "secret-key" not in repr_str
        assert "Pinecone" in repr_str
        assert "base_url" in repr_str

    def test_async_pinecone_repr_no_api_key(self):
        """Verify AsyncPinecone __repr__ doesn't leak API key."""
        pc = AsyncPinecone(api_key="secret-key-12345")
        repr_str = repr(pc)

        assert "secret-key" not in repr_str
        assert "AsyncPinecone" in repr_str
        assert "base_url" in repr_str


class TestClientDir:
    """Test __dir__ for client classes."""

    def test_pinecone_dir_public_only(self):
        """Verify Pinecone __dir__ shows only public API."""
        pc = Pinecone(api_key="test-key")
        dir_result = dir(pc)

        # Verify public methods/properties included
        assert "inference" in dir_result
        assert "close" in dir_result

        # Verify internals excluded
        assert "_api_key" not in dir_result
        assert "_http_client" not in dir_result
        assert "_base_url" not in dir_result

        # Verify no underscore-prefixed items
        private_items = [x for x in dir_result if x.startswith("_")]
        assert len(private_items) == 0, f"Found private items: {private_items}"

    def test_async_pinecone_dir_public_only(self):
        """Verify AsyncPinecone __dir__ shows only public API."""
        pc = AsyncPinecone(api_key="test-key")
        dir_result = dir(pc)

        # Verify public methods/properties included
        assert "inference" in dir_result
        assert "close" in dir_result

        # Verify internals excluded
        assert "_api_key" not in dir_result
        assert "_http_client" not in dir_result

        # Verify no underscore-prefixed items
        private_items = [x for x in dir_result if x.startswith("_")]
        assert len(private_items) == 0, f"Found private items: {private_items}"


class TestResponseRepr:
    """Test __repr__ for response classes."""

    def test_embed_response_repr_truncates_long_arrays(self):
        """Verify EmbedResponse shows summary, not full data."""
        embeddings = [Embedding(values=[0.1] * 1536, index=i) for i in range(1000)]
        response = EmbedResponse(
            data=embeddings, model="test-model", vector_type="dense", usage=Usage(total_tokens=5000)
        )

        repr_str = repr(response)

        # Should show summary info
        assert "1000" in repr_str or "embeddings=1000" in repr_str
        assert "test-model" in repr_str
        assert "Usage" in repr_str

        # Should NOT contain full array contents
        assert repr_str.count("0.1") < 10  # Not all 1,536,000 values
        assert len(repr_str) < 200  # Reasonable length

    def test_rerank_response_repr_shows_summary(self):
        """Verify RerankResponse shows summary."""
        results = [RerankResult(index=i, score=0.9 - i * 0.1) for i in range(10)]
        response = RerankResponse(data=results, model="test-model", usage=Usage(total_tokens=100))

        repr_str = repr(response)

        assert "10" in repr_str or "results=10" in repr_str
        assert "test-model" in repr_str

    def test_list_models_response_repr_shows_count(self):
        """Verify ListModelsResponse shows model count."""
        models = [
            ModelInfo(
                model=f"model-{i}",
                short_description="Test model",
                type="embed",
                modality="text",
                max_sequence_length=512,
                max_batch_size=32,
                provider_name="test",
                supported_parameters=[],
            )
            for i in range(5)
        ]
        response = ListModelsResponse(models=models)

        repr_str = repr(response)

        assert "5" in repr_str or "models=5" in repr_str

    def test_embedding_repr_truncates_large_vector(self):
        """Verify Embedding truncates large values array."""
        emb = Embedding(values=[0.1, 0.2, 0.3] + [0.0] * 1533, index=0)
        repr_str = repr(emb)

        # Should show first 2 values + ellipsis
        assert "0.1" in repr_str
        assert "0.2" in repr_str
        assert "..." in repr_str

        # Should not show all 1536 values
        assert repr_str.count("0.") < 10


class TestResponseDir:
    """Test __dir__ for response classes."""

    def test_embed_response_dir_hides_cache(self):
        """Verify EmbedResponse __dir__ hides cache attributes."""
        response = EmbedResponse(
            data=[Embedding(values=[0.1, 0.2], index=0)],
            model="test-model",
            vector_type="dense",
            usage=Usage(total_tokens=5),
        )

        dir_result = dir(response)

        # Verify public attributes included
        assert "data" in dir_result
        assert "embeddings" in dir_result
        assert "model" in dir_result
        assert "usage" in dir_result
        assert "vector_type" in dir_result

        # Verify cache attribute excluded
        assert "_cached_embeddings" not in dir_result

        # Verify no underscore-prefixed items
        private_items = [x for x in dir_result if x.startswith("_")]
        assert len(private_items) == 0, f"Found private items: {private_items}"


class TestValidationHelpers:
    """Test that validation helpers enforce interactive discovery patterns."""

    def test_validate_pinecone_client(self):
        """Verify Pinecone client passes all validations."""
        api_key = "test-secret-key-12345"
        pc = Pinecone(api_key=api_key)

        # Should pass all validations
        validate_client_class(Pinecone, pc, api_key)

    def test_validate_async_pinecone_client(self):
        """Verify AsyncPinecone client passes all validations."""
        api_key = "test-secret-key-12345"
        pc = AsyncPinecone(api_key=api_key)

        # Should pass all validations
        validate_client_class(AsyncPinecone, pc, api_key)

    def test_validate_inference_namespace(self):
        """Verify Inference namespace passes all validations."""
        api_key = "test-key"
        pc = Pinecone(api_key=api_key)

        # Should pass all validations
        validate_client_class(Inference, pc.inference, api_key)

    def test_validate_async_inference_namespace(self):
        """Verify AsyncInference namespace passes all validations."""
        api_key = "test-key"
        pc = AsyncPinecone(api_key=api_key)

        # Should pass all validations
        validate_client_class(AsyncInference, pc.inference, api_key)

    def test_validate_embed_response(self):
        """Verify EmbedResponse passes all validations."""
        response = EmbedResponse(
            data=[Embedding(values=[0.1, 0.2], index=0)],
            model="test-model",
            vector_type="dense",
            usage=Usage(total_tokens=5),
        )

        # Should pass all validations including rich representations
        validate_response_class(EmbedResponse, response, require_rich_repr=True)

    def test_validate_rerank_response(self):
        """Verify RerankResponse passes all validations."""
        response = RerankResponse(
            data=[RerankResult(index=0, score=0.9)],
            model="test-model",
            usage=Usage(total_tokens=10),
        )

        # Should pass all validations including rich representations
        validate_response_class(RerankResponse, response, require_rich_repr=True)

    def test_validate_list_models_response(self):
        """Verify ListModelsResponse passes all validations."""
        models = [
            ModelInfo(
                model="model-1",
                short_description="Test",
                type="embed",
                modality="text",
                max_sequence_length=512,
                max_batch_size=32,
                provider_name="test",
                supported_parameters=[],
            )
        ]
        response = ListModelsResponse(models=models)

        # Should pass all validations including rich representations
        validate_response_class(ListModelsResponse, response, require_rich_repr=True)

    def test_validation_fails_for_missing_custom_repr(self):
        """Verify validation catches missing custom __repr__."""

        class BadClass:
            pass  # Uses default object.__repr__

        with pytest.raises(InteractiveValidationError, match="Must implement custom __repr__"):
            assert_has_custom_repr(BadClass, BadClass())

    def test_validation_fails_for_missing_custom_dir(self):
        """Verify validation catches missing custom __dir__."""

        class BadClass:
            pass  # Uses default object.__dir__

        with pytest.raises(InteractiveValidationError, match="implement custom __dir__"):
            assert_has_custom_dir(BadClass, BadClass())

    def test_validation_fails_for_exposed_secrets(self):
        """Verify validation catches API key leaks."""

        class LeakyClass:
            def __init__(self):
                self.api_key = "secret-key"

            def __repr__(self):
                return f"LeakyClass(api_key='{self.api_key}')"

        with pytest.raises(InteractiveValidationError, match="leaks secret"):
            assert_repr_hides_secrets(LeakyClass(), ["secret-key"])

    def test_validation_fails_for_missing_rich_repr(self):
        """Verify validation catches missing _repr_pretty_."""

        class NoRichClass:
            def __repr__(self):
                return "NoRichClass()"

        with pytest.raises(InteractiveValidationError, match="should implement _repr_pretty_"):
            assert_has_repr_pretty(NoRichClass, NoRichClass())


class TestRichRepresentations:
    """Test optional rich representations (_repr_pretty_, _repr_html_)."""

    def test_embed_response_repr_pretty(self):
        """Verify _repr_pretty_ doesn't crash."""

        class MockPrinter:
            def __init__(self):
                self.buffer = StringIO()

            def text(self, s):
                self.buffer.write(s)

            def breakable(self):
                self.buffer.write("\n")

            def group(self, *args, **kwargs):
                return self

            def __enter__(self):
                return self

            def __exit__(self, *args):
                pass

        response = EmbedResponse(
            data=[Embedding(values=[0.1, 0.2], index=0)],
            model="test-model",
            vector_type="dense",
            usage=Usage(total_tokens=5),
        )

        p = MockPrinter()
        response._repr_pretty_(p, cycle=False)
        output = p.buffer.getvalue()

        assert "test-model" in output
        assert "embeddings=1" in output or "1" in output

        # Test cycle detection
        p2 = MockPrinter()
        response._repr_pretty_(p2, cycle=True)
        output2 = p2.buffer.getvalue()
        assert "..." in output2

    def test_embed_response_repr_html_escapes_user_data(self):
        """Verify _repr_html_ prevents XSS."""
        response = EmbedResponse(
            data=[Embedding(values=[0.1], index=0)],
            model="<script>alert('xss')</script>",
            vector_type="dense",
            usage=Usage(total_tokens=5),
        )

        html = response._repr_html_()

        assert isinstance(html, str)
        assert "<script>" not in html  # Script tag should be escaped
        assert "&lt;script&gt;" in html  # Should be HTML-escaped
        assert "test-model" not in html  # Original unescaped version not present

    def test_rerank_response_repr_html(self):
        """Verify RerankResponse _repr_html_ returns valid HTML."""
        response = RerankResponse(
            data=[RerankResult(index=0, score=0.9)],
            model="test-model",
            usage=Usage(total_tokens=10),
        )

        html = response._repr_html_()

        assert isinstance(html, str)
        assert "<" in html
        assert ">" in html
        assert "test-model" in html
        assert "1" in html  # Result count

    def test_list_models_response_repr_html(self):
        """Verify ListModelsResponse _repr_html_ returns valid HTML."""
        models = [
            ModelInfo(
                model="model-1",
                short_description="Test",
                type="embed",
                modality="text",
                max_sequence_length=512,
                max_batch_size=32,
                provider_name="test",
                supported_parameters=[],
            )
        ]
        response = ListModelsResponse(models=models)

        html = response._repr_html_()

        assert isinstance(html, str)
        assert "<" in html
        assert ">" in html
        assert "1" in html  # Model count
