"""Tests for synchronous OdinsEyeClient."""

from __future__ import annotations

import tempfile
import json
from pathlib import Path

import httpx
import pytest
from pydantic import ValidationError
from pytest_httpx import HTTPXMock

from odins_eye import OdinsEyeAPIError, OdinsEyeClient, OdinsEyeError
from odins_eye.client import RetryConfig


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

    def test_init_with_api_key(self, mock_api_key):
        """Test client initialization with API key."""
        client = OdinsEyeClient(api_key=mock_api_key)
        assert client._client.headers["x-api-key"] == mock_api_key
        client.close()

    def test_init_without_api_key(self):
        """Test client initialization without API key."""
        client = OdinsEyeClient()
        assert "x-api-key" not in client._client.headers
        client.close()

    def test_init_with_custom_headers(self):
        """Test client initialization with custom headers."""
        custom_headers = {"X-Custom": "value"}
        client = OdinsEyeClient(headers=custom_headers)
        assert client._client.headers["X-Custom"] == "value"
        client.close()

    def test_init_with_timeout(self):
        """Test client initialization with custom timeout."""
        timeout = httpx.Timeout(30.0, connect=10.0)
        client = OdinsEyeClient(timeout=timeout)
        assert client._client.timeout.read == 30.0
        client.close()

    def test_context_manager(self, mock_api_key):
        """Test client as context manager."""
        with OdinsEyeClient(api_key=mock_api_key) as client:
            assert isinstance(client, OdinsEyeClient)


class TestIndexEndpoint:
    """Tests for index endpoint."""

    def test_index_success(self, httpx_mock: HTTPXMock, mock_status_response):
        """Test successful index call."""
        httpx_mock.add_response(json=mock_status_response)
        with OdinsEyeClient() as client:
            result = client.index()
            assert result["status"] == 200
            assert result["message"] == "API is running"

    def test_index_http_error(self, httpx_mock: HTTPXMock):
        """Test index call with HTTP error."""
        httpx_mock.add_response(
            status_code=500, json={"error": "Internal server error"}
        )
        with OdinsEyeClient(retry_config=RetryConfig(max_retries=0)) as client:
            with pytest.raises(OdinsEyeAPIError) as exc_info:
                client.index()
            assert exc_info.value.status_code == 500
            assert "Internal server error" in str(exc_info.value)


class TestDocumentEndpoint:
    """Tests for document endpoint."""

    def test_document_success(self, httpx_mock: HTTPXMock, mock_es_response):
        """Test successful document retrieval."""
        httpx_mock.add_response(json=mock_es_response)
        with OdinsEyeClient() as client:
            result = client.document("doc123")
            assert result["statusCode"] == 200

    def test_document_missing_id(self):
        """Test document call without doc_id."""
        with OdinsEyeClient() as client:
            with pytest.raises(ValueError, match="doc_id is required"):
                client.document("")


class TestQueryEndpoint:
    """Tests for query endpoint."""

    def test_query_success(self, httpx_mock: HTTPXMock, mock_query_response):
        """Test successful query."""
        httpx_mock.add_response(json=mock_query_response)
        with OdinsEyeClient() as client:
            query_data = {"query": {"match_all": {}}}
            result = client.query(query_data)
            assert result["status"] == 200

    def test_query_missing_required_query(self):
        """Test query validation requires query key."""
        with OdinsEyeClient() as client:
            with pytest.raises(ValidationError, match="query field is required"):
                client.query({})


class TestProfileEndpoint:
    """Tests for profile endpoint."""

    def test_profile_success(self, httpx_mock: HTTPXMock, mock_profile_response):
        """Test successful profile retrieval."""
        httpx_mock.add_response(json=mock_profile_response)
        with OdinsEyeClient() as client:
            result = client.profile()
            assert result["status"] == 200
            assert result["user"]["name"] == "Test User"


class TestVersionEndpoint:
    """Tests for version endpoint."""

    def test_version_success(self, httpx_mock: HTTPXMock, mock_version_response):
        """Test successful version retrieval."""
        httpx_mock.add_response(json=mock_version_response)
        with OdinsEyeClient() as client:
            result = client.version()
            assert result["status"] == 200
            assert result["version"] == "1.0.0"


class TestCreditsEndpoint:
    """Tests for credits endpoint."""

    def test_credits_success(self, httpx_mock: HTTPXMock, mock_credits_response):
        """Test successful credits retrieval."""
        httpx_mock.add_response(json=mock_credits_response)
        with OdinsEyeClient() as client:
            result = client.credits()
            assert result["status"] == 200
            assert result["current_balance"] == 1000


class TestFaceSearchEndpoint:
    """Tests for face search endpoint."""

    def test_face_search_success(self, httpx_mock: HTTPXMock):
        """Test successful face search."""
        httpx_mock.add_response(json={"status": 200, "results": []})

        # Create a temporary image file
        with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
            tmp.write(b"fake image data")
            tmp_path = tmp.name

        try:
            with OdinsEyeClient() as client:
                result = client.face_search(tmp_path)
                assert result["status"] == 200
        finally:
            Path(tmp_path).unlink()

    def test_face_search_file_not_found(self):
        """Test face search with non-existent file."""
        with OdinsEyeClient() as client:
            with pytest.raises(ValueError, match="image_path not found"):
                client.face_search("/nonexistent/path.jpg")

    def test_face_search_direct_parameters(self, httpx_mock: HTTPXMock):
        """Test direct face-search parameters are forwarded."""
        httpx_mock.add_response(json={"status": 200, "results": []})

        with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
            tmp.write(b"fake image data")
            tmp_path = tmp.name

        try:
            with OdinsEyeClient() as client:
                client.face_search(tmp_path, max_k=7, confidence=0.8, min_score=0.75)

            request = httpx_mock.get_requests()[0]
            payload = json.loads(request.content.decode("utf-8"))
            assert payload["max_k"] == 7
            assert payload["confidence"] == 0.8
            assert payload["min_score"] == 0.75
            assert "image_base64" in payload
        finally:
            Path(tmp_path).unlink()


class TestNettestEndpoint:
    """Tests for nettest endpoint."""

    def test_nettest_success(self, httpx_mock: HTTPXMock):
        """Test successful network test."""
        httpx_mock.add_response(json={"status": "ok"})
        with OdinsEyeClient() as client:
            result = client.nettest()
            assert result["status"] == "ok"


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

    def test_non_json_response(self, httpx_mock: HTTPXMock):
        """Test handling of non-JSON response."""
        httpx_mock.add_response(text="Not JSON")
        with OdinsEyeClient() as client:
            with pytest.raises(OdinsEyeError, match="non-JSON response"):
                client.index()

    def test_validation_error(self, httpx_mock: HTTPXMock):
        """Test handling of validation error."""
        httpx_mock.add_response(json={"invalid": "data"})
        with OdinsEyeClient() as client:
            with pytest.raises(OdinsEyeError, match="validation failed"):
                client.profile()

    def test_api_error_with_details(self, httpx_mock: HTTPXMock, mock_error_response):
        """Test API error with error details."""
        httpx_mock.add_response(status_code=400, json=mock_error_response)
        with OdinsEyeClient() as client:
            with pytest.raises(OdinsEyeAPIError) as exc_info:
                client.index()
            assert exc_info.value.status_code == 400
            assert exc_info.value.error == "Invalid request"
            assert exc_info.value.error_details == "Missing required field"

    def test_api_error_without_json(self, httpx_mock: HTTPXMock):
        """Test API error with non-JSON response."""
        httpx_mock.add_response(status_code=500, text="Internal Server Error")
        with OdinsEyeClient(retry_config=RetryConfig(max_retries=0)) as client:
            with pytest.raises(OdinsEyeAPIError) as exc_info:
                client.index()
            assert exc_info.value.status_code == 500
            assert exc_info.value.response_text == "Internal Server Error"

    def test_api_error_with_non_object_json(self, httpx_mock: HTTPXMock):
        """Test API error with non-object JSON response."""
        httpx_mock.add_response(status_code=500, json=["error"])
        with OdinsEyeClient(retry_config=RetryConfig(max_retries=0)) as client:
            with pytest.raises(OdinsEyeAPIError) as exc_info:
                client.index()
            assert exc_info.value.status_code == 500
            assert exc_info.value.error is None
            assert exc_info.value.response_text is not None

    def test_rate_limit_does_not_sleep_on_final_attempt(
        self, httpx_mock: HTTPXMock, monkeypatch: pytest.MonkeyPatch
    ):
        """Test 429 on final attempt raises immediately without sleep."""
        called = False

        def fake_sleep(_delay: float) -> None:
            nonlocal called
            called = True

        monkeypatch.setattr("odins_eye.client.time.sleep", fake_sleep)
        httpx_mock.add_response(status_code=429, headers={"Retry-After": "10"})

        with OdinsEyeClient(retry_config=RetryConfig(max_retries=0)) as client:
            with pytest.raises(OdinsEyeAPIError) as exc_info:
                client.index()
            assert exc_info.value.status_code == 429
            assert called is False
