import logging
import os
from unittest.mock import Mock

import pytest
from dotenv import load_dotenv
from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout

from swarms.models.gpt4v import GPT4Vision, GPT4VisionResponse

load_dotenv

api_key = os.getenv("OPENAI_API_KEY")


# Mock the OpenAI client
@pytest.fixture
def mock_openai_client():
    return Mock()


@pytest.fixture
def gpt4vision(mock_openai_client):
    return GPT4Vision(client=mock_openai_client)


def test_gpt4vision_default_values():
    # Arrange and Act
    gpt4vision = GPT4Vision()

    # Assert
    assert gpt4vision.max_retries == 3
    assert gpt4vision.model == "gpt-4-vision-preview"
    assert gpt4vision.backoff_factor == 2.0
    assert gpt4vision.timeout_seconds == 10
    assert gpt4vision.api_key is None
    assert gpt4vision.quality == "low"
    assert gpt4vision.max_tokens == 200


def test_gpt4vision_api_key_from_env_variable():
    # Arrange
    api_key = os.environ["OPENAI_API_KEY"]

    # Act
    gpt4vision = GPT4Vision()

    # Assert
    assert gpt4vision.api_key == api_key


def test_gpt4vision_set_api_key():
    # Arrange
    gpt4vision = GPT4Vision(api_key=api_key)

    # Assert
    assert gpt4vision.api_key == api_key


def test_gpt4vision_invalid_max_retries():
    # Arrange and Act
    with pytest.raises(ValueError):
        GPT4Vision(max_retries=-1)


def test_gpt4vision_invalid_backoff_factor():
    # Arrange and Act
    with pytest.raises(ValueError):
        GPT4Vision(backoff_factor=-1)


def test_gpt4vision_invalid_timeout_seconds():
    # Arrange and Act
    with pytest.raises(ValueError):
        GPT4Vision(timeout_seconds=-1)


def test_gpt4vision_invalid_max_tokens():
    # Arrange and Act
    with pytest.raises(ValueError):
        GPT4Vision(max_tokens=-1)


def test_gpt4vision_logger_initialized():
    # Arrange
    gpt4vision = GPT4Vision()

    # Assert
    assert isinstance(gpt4vision.logger, logging.Logger)


def test_gpt4vision_process_img_nonexistent_file():
    # Arrange
    gpt4vision = GPT4Vision()
    img_path = "nonexistent_image.jpg"

    # Act and Assert
    with pytest.raises(FileNotFoundError):
        gpt4vision.process_img(img_path)


def test_gpt4vision_call_single_task_single_image_no_openai_client(gpt4vision):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    # Act and Assert
    with pytest.raises(AttributeError):
        gpt4vision(img_url, [task])


def test_gpt4vision_call_single_task_single_image_empty_response(
    gpt4vision, mock_openai_client
):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    mock_openai_client.chat.completions.create.return_value.choices = []

    # Act
    response = gpt4vision(img_url, [task])

    # Assert
    assert response.answer == ""
    mock_openai_client.chat.completions.create.assert_called_once()


def test_gpt4vision_call_multiple_tasks_single_image_empty_responses(
    gpt4vision, mock_openai_client
):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    tasks = ["Describe this image.", "What's in this picture?"]

    mock_openai_client.chat.completions.create.return_value.choices = []

    # Act
    responses = gpt4vision(img_url, tasks)

    # Assert
    assert all(response.answer == "" for response in responses)
    assert (
        mock_openai_client.chat.completions.create.call_count == 1
    )  # Should be called only once


def test_gpt4vision_call_single_task_single_image_timeout(
    gpt4vision, mock_openai_client
):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    mock_openai_client.chat.completions.create.side_effect = Timeout(
        "Request timed out"
    )

    # Act and Assert
    with pytest.raises(Timeout):
        gpt4vision(img_url, [task])


def test_gpt4vision_call_retry_with_success_after_timeout(
    gpt4vision, mock_openai_client
):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    # Simulate success after a timeout and retry
    mock_openai_client.chat.completions.create.side_effect = [
        Timeout("Request timed out"),
        {
            "choices": [
                {"message": {"content": {"text": "A description of the image."}}}
            ],
        },
    ]

    # Act
    response = gpt4vision(img_url, [task])

    # Assert
    assert response.answer == "A description of the image."
    assert (
        mock_openai_client.chat.completions.create.call_count == 2
    )  # Should be called twice


def test_gpt4vision_process_img():
    # Arrange
    img_path = "test_image.jpg"
    gpt4vision = GPT4Vision()

    # Act
    img_data = gpt4vision.process_img(img_path)

    # Assert
    assert img_data.startswith("/9j/")  # Base64-encoded image data


def test_gpt4vision_call_single_task_single_image(gpt4vision, mock_openai_client):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    expected_response = GPT4VisionResponse(answer="A description of the image.")

    mock_openai_client.chat.completions.create.return_value.choices[
        0
    ].text = expected_response.answer

    # Act
    response = gpt4vision(img_url, [task])

    # Assert
    assert response == expected_response
    mock_openai_client.chat.completions.create.assert_called_once()


def test_gpt4vision_call_single_task_multiple_images(gpt4vision, mock_openai_client):
    # Arrange
    img_urls = ["https://example.com/image1.jpg", "https://example.com/image2.jpg"]
    task = "Describe these images."

    expected_response = GPT4VisionResponse(answer="Descriptions of the images.")

    mock_openai_client.chat.completions.create.return_value.choices[
        0
    ].text = expected_response.answer

    # Act
    response = gpt4vision(img_urls, [task])

    # Assert
    assert response == expected_response
    mock_openai_client.chat.completions.create.assert_called_once()


def test_gpt4vision_call_multiple_tasks_single_image(gpt4vision, mock_openai_client):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    tasks = ["Describe this image.", "What's in this picture?"]

    expected_responses = [
        GPT4VisionResponse(answer="A description of the image."),
        GPT4VisionResponse(answer="It contains various objects."),
    ]

    def create_mock_response(response):
        return {"choices": [{"message": {"content": {"text": response.answer}}}]}

    mock_openai_client.chat.completions.create.side_effect = [
        create_mock_response(response) for response in expected_responses
    ]

    # Act
    responses = gpt4vision(img_url, tasks)

    # Assert
    assert responses == expected_responses
    assert (
        mock_openai_client.chat.completions.create.call_count == 1
    )  # Should be called only once

    def test_gpt4vision_call_multiple_tasks_single_image(
        gpt4vision, mock_openai_client
    ):
        # Arrange
        img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
        tasks = ["Describe this image.", "What's in this picture?"]

        expected_responses = [
            GPT4VisionResponse(answer="A description of the image."),
            GPT4VisionResponse(answer="It contains various objects."),
        ]

        mock_openai_client.chat.completions.create.side_effect = [
            {
                "choices": [
                    {"message": {"content": {"text": expected_responses[i].answer}}}
                ]
            }
            for i in range(len(expected_responses))
        ]

        # Act
        responses = gpt4vision(img_url, tasks)

        # Assert
        assert responses == expected_responses
        assert (
            mock_openai_client.chat.completions.create.call_count == 1
        )  # Should be called only once


def test_gpt4vision_call_multiple_tasks_multiple_images(gpt4vision, mock_openai_client):
    # Arrange
    img_urls = [
        "https://images.unsplash.com/photo-1694734479857-626882b6db37?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
        "https://images.unsplash.com/photo-1694734479898-6ac4633158ac?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
    ]
    tasks = ["Describe these images.", "What's in these pictures?"]

    expected_responses = [
        GPT4VisionResponse(answer="Descriptions of the images."),
        GPT4VisionResponse(answer="They contain various objects."),
    ]

    mock_openai_client.chat.completions.create.side_effect = [
        {"choices": [{"message": {"content": {"text": response.answer}}}]}
        for response in expected_responses
    ]

    # Act
    responses = gpt4vision(img_urls, tasks)

    # Assert
    assert responses == expected_responses
    assert (
        mock_openai_client.chat.completions.create.call_count == 1
    )  # Should be called only once


def test_gpt4vision_call_http_error(gpt4vision, mock_openai_client):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    mock_openai_client.chat.completions.create.side_effect = HTTPError("HTTP Error")

    # Act and Assert
    with pytest.raises(HTTPError):
        gpt4vision(img_url, [task])


def test_gpt4vision_call_request_error(gpt4vision, mock_openai_client):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    mock_openai_client.chat.completions.create.side_effect = RequestException(
        "Request Error"
    )

    # Act and Assert
    with pytest.raises(RequestException):
        gpt4vision(img_url, [task])


def test_gpt4vision_call_connection_error(gpt4vision, mock_openai_client):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    mock_openai_client.chat.completions.create.side_effect = ConnectionError(
        "Connection Error"
    )

    # Act and Assert
    with pytest.raises(ConnectionError):
        gpt4vision(img_url, [task])


def test_gpt4vision_call_retry_with_success(gpt4vision, mock_openai_client):
    # Arrange
    img_url = "https://images.unsplash.com/photo-1694734479942-8cc7f4660578?q=80&w=1287&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
    task = "Describe this image."

    # Simulate success after a retry
    mock_openai_client.chat.completions.create.side_effect = [
        RequestException("Temporary error"),
        {
            "choices": [{"text": "A description of the image."}]
        },  # fixed dictionary syntax
    ]

    # Act
    response = gpt4vision(img_url, [task])

    # Assert
    assert response.answer == "A description of the image."
    assert (
        mock_openai_client.chat.completions.create.call_count == 2
    )  # Should be called twice