from collections.abc import Generator
from unittest.mock import MagicMock, mock_open, patch

import pytest

from sqgen_risk.client import Client
from sqgen_risk.config import Settings
from sqgen_risk.exceptions import APIError, AuthenticationError
from sqgen_risk.models import InferenceRequest, Model


@pytest.fixture
def mock_session() -> Generator[MagicMock, None, None]:
    """Return a mocked session."""
    with patch("sqgen_risk.client.requests.Session") as mock:
        yield mock


@pytest.fixture
def client(mock_session, test_settings) -> Client:
    """Return a Client instance for testing with mocked session."""
    from tests.conftest import TEST_EMAIL, TEST_PASSWORD

    # Set up mock for authentication
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"session": {"access_token": "test-token-not-real"}}
    mock_session.return_value.post.return_value = mock_response

    client = Client()
    client.authenticate(email=TEST_EMAIL, password=TEST_PASSWORD)
    return client


@patch("sqgen_risk.client.requests.Session")
def test_client_initialization(mock_session) -> None:
    """Test client initialization."""

    # Test with default settings
    client = Client()
    assert client.base_url == Settings().BASE_URL
    assert client._token is None


@patch("sqgen_risk.client.requests.Session")
def test_authenticate_success(mock_session) -> None:
    """Test successful authentication."""
    from tests.conftest import TEST_EMAIL, TEST_PASSWORD

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"session": {"access_token": "new-test-token"}}
    mock_session.return_value.post.return_value = mock_response

    client = Client()
    token = client.authenticate(email=TEST_EMAIL, password=TEST_PASSWORD)

    assert token == "new-test-token"  # noqa: S105
    mock_session.return_value.headers.update.assert_called_with({"Authorization": "Bearer new-test-token"})


@patch("sqgen_risk.client.requests.Session")
def test_authenticate_failure(mock_session) -> None:
    """Test authentication failure."""
    from tests.conftest import TEST_EMAIL, TEST_PASSWORD

    mock_response = MagicMock()
    mock_response.status_code = 401
    mock_session.return_value.post.return_value = mock_response

    client = Client()

    with pytest.raises(AuthenticationError):
        client.authenticate(email=TEST_EMAIL, password=TEST_PASSWORD)


@patch("sqgen_risk.client.requests.Session")
def test_authenticate_no_token(mock_session) -> None:
    """Test authentication with no token in response."""
    from tests.conftest import TEST_EMAIL, TEST_PASSWORD

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"session": {}}  # No token in response
    mock_session.return_value.post.return_value = mock_response

    client = Client()

    with pytest.raises(AuthenticationError):
        client.authenticate(email=TEST_EMAIL, password=TEST_PASSWORD)


def test_list_models(mock_session, client) -> None:
    """Test listing models."""
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = [
        {"id": 1, "model_id": "model-1", "user_id": "user-1", "name": "Model 1"},
        {"id": 2, "model_id": "model-2", "user_id": "user-1", "name": "Model 2"},
    ]
    mock_session.return_value.get.return_value = mock_response

    models = client.list_models()

    assert len(models) == 2
    assert isinstance(models[0], Model)
    assert models[0].model_id == "model-1"
    assert models[1].name == "Model 2"


def test_list_models_error(mock_session, client) -> None:
    """Test listing models with error."""
    mock_response = MagicMock()
    mock_response.status_code = 500
    mock_session.return_value.get.return_value = mock_response

    with pytest.raises(APIError):
        client.list_models()


def test_create_inference(mock_session, client) -> None:
    """Test creating an inference request."""
    mock_response = MagicMock()
    mock_response.status_code = 201
    mock_response.json.return_value = {
        "id": 1,
        "user_id": "user-1",
        "inference_request_id": "request-1",
        "model_id": "model-1",
        "created_at": "2025-06-29T12:00:00",
    }
    mock_session.return_value.post.return_value = mock_response

    with patch("builtins.open", mock_open(read_data="test data")):
        inference = client.create_inference(model_id="model-1", file_path="data.csv")

    assert isinstance(inference, InferenceRequest)
    assert inference.inference_request_id == "request-1"
    assert inference.model_id == "model-1"


def test_create_inference_error(mock_session, client) -> None:
    """Test creating an inference request with error."""
    mock_response = MagicMock()
    mock_response.status_code = 400
    mock_session.return_value.post.return_value = mock_response

    with patch("builtins.open", mock_open(read_data="test data")), pytest.raises(APIError):
        client.create_inference(model_id="model-1", file_path="data.csv")


def test_get_inference_result_complete(mock_session, client) -> None:
    """Test getting a completed inference result."""
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.content = b"result data"
    mock_session.return_value.get.return_value = mock_response

    result = client.get_inference_result(inference_request_id="request-1")

    assert result == b"result data"


def test_get_inference_result_processing(mock_session, client) -> None:
    """Test getting an inference result that's still processing."""
    mock_response = MagicMock()
    mock_response.status_code = 202
    mock_session.return_value.get.return_value = mock_response

    result = client.get_inference_result(inference_request_id="request-1")

    assert result is None


def test_get_inference_result_error(mock_session, client) -> None:
    """Test getting an inference result with error."""
    mock_response = MagicMock()
    mock_response.status_code = 404
    mock_session.return_value.get.return_value = mock_response

    with pytest.raises(APIError):
        client.get_inference_result(inference_request_id="request-1")


def test_list_inferences(mock_session, client) -> None:
    """Test listing inference requests."""
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.json.return_value = [
        {
            "id": 1,
            "user_id": "user-1",
            "inference_request_id": "request-1",
            "model_id": "model-1",
            "created_at": "2025-06-29T12:00:00",
        },
        {
            "id": 2,
            "user_id": "user-1",
            "inference_request_id": "request-2",
            "model_id": "model-1",
            "created_at": "2025-06-30T12:00:00",
        },
    ]
    mock_session.return_value.get.return_value = mock_response

    inferences = client.list_inferences()

    assert len(inferences) == 2
    assert isinstance(inferences[0], InferenceRequest)
    assert inferences[0].inference_request_id == "request-1"
    assert inferences[1].inference_request_id == "request-2"


def test_list_inferences_error(mock_session, client) -> None:
    """Test listing inference requests with error."""
    mock_response = MagicMock()
    mock_response.status_code = 500
    mock_session.return_value.get.return_value = mock_response

    with pytest.raises(APIError):
        client.list_inferences()
