import pytest

from .helpers.collection_setup import basic_collection_setup, drop_collection
from .helpers.helpers import request_with_validation


@pytest.fixture(autouse=True, scope="module")
def setup(on_disk_vectors, collection_name):
    basic_collection_setup(collection_name=collection_name, on_disk_vectors=on_disk_vectors)
    yield
    drop_collection(collection_name=collection_name)


def test_default_is_avg_vector(collection_name):
    params = {
        "positive": [1, 2],
        "negative": [3, 4],
        "exact": True,
        "limit": 10,
    }

    default_response = request_with_validation(
        api="/collections/{collection_name}/points/recommend",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            **params,
        },
    )
    assert default_response.ok

    # we should only get 4 because there are 8 vectors and we used 4 as examples
    assert len(default_response.json()["result"]) == 4

    avg_response = request_with_validation(
        api="/collections/{collection_name}/points/recommend",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            **params,
            "strategy": "average_vector",
        },
    )
    assert avg_response.ok
    assert len(avg_response.json()["result"]) == 4

    assert default_response.json()["result"] == avg_response.json()["result"]


def test_single_vs_batch(collection_name):
    # Bunch of valid examples
    params_list = [
        {
            "positive": [1, 2],
            "negative": [3, 4],
            "limit": 1,
        },
        {
            "positive": [1],
            "negative": [3, 4],
            "limit": 1,
        },
        {
            # no negative because it's optional with this strategy
            "negative": [4, 5],
            "exact": True,
            "strategy": "best_score",
            "limit": 1,
        },
        {
            "positive": [2, 3],
            "negative": [4, 5],
            "strategy": "best_score",
            "limit": 1,
        },
        {
            "positive": [2, 3],
            "negative": [4, 5],
            "exact": True,
            "strategy": "best_score",
            "limit": 1,
        },
        {
            "positive": [8],
            "negative": [],
            "exact": True,
            "strategy": "average_vector",
            "limit": 1,
        },
    ]

    batch_response = request_with_validation(
        api="/collections/{collection_name}/points/recommend/batch",
        method="POST",
        path_params={"collection_name": collection_name},
        body={"searches": params_list},
    )

    assert batch_response.ok
    assert len(batch_response.json()["result"]) == len(params_list)

    # Compare against sequential single searches
    for i, params in enumerate(params_list):
        single_response = request_with_validation(
            api="/collections/{collection_name}/points/recommend",
            method="POST",
            path_params={"collection_name": collection_name},
            body=params,
        )
        assert single_response.ok
        assert single_response.json()["result"] == batch_response.json()["result"][i]


def test_without_positives(collection_name):
    def req_with_positives(positive, strategy=None):
        if strategy is None:
            strat_dict = {}
        else:
            strat_dict = {"strategy": strategy}

        return request_with_validation(
            api="/collections/{collection_name}/points/recommend",
            method="POST",
            path_params={"collection_name": collection_name},
            body={
                "positive": positive,
                **strat_dict,
                "limit": 2,
            },
        )

    # Assert this is valid
    response = req_with_positives([1, 2])
    assert response.ok

    # But all these are not
    response = req_with_positives([])
    assert response.status_code == 400

    response = req_with_positives([], "average_vector")
    assert response.status_code == 400

    # Also no negative and no positive is invalid with best_score
    response = req_with_positives([], "best_score")
    assert response.status_code == 400


def test_best_score_works_with_only_negatives(collection_name):
    response = request_with_validation(
        api="/collections/{collection_name}/points/recommend",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            "negative": [1, 2],
            "strategy": "best_score",
            "limit": 5,
        },
    )
    assert response.ok
    assert len(response.json()["result"]) == 5

    # All scores should be negative
    for result in response.json()["result"]:
        assert result["score"] < 0


def test_only_1_positive_in_best_score_is_equivalent_to_normal_search(collection_name):
    limit = 4

    # recommendation response
    reco_response = request_with_validation(
        api="/collections/{collection_name}/points/recommend",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            "positive": [1],
            "strategy": "best_score",
            "limit": limit,
            "exact": True,
        },
    )
    assert reco_response.ok
    assert len(reco_response.json()["result"]) == limit

    # Get vector from point 1
    vector = get_points(collection_name, [1])[0]["vector"]

    # Use normal search with that vector
    search_response = request_with_validation(
        api="/collections/{collection_name}/points/search",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            "vector": vector,
            "filter": {"must_not": [{"has_id": [1]}]},
            "limit": limit,
            "exact": True,
        },
    )

    assert search_response.ok
    assert len(search_response.json()["result"]) == limit

    # Scores can be different, but the ids and order should be the same
    reco_ids = [result["id"] for result in reco_response.json()["result"]]
    search_ids = [result["id"] for result in search_response.json()["result"]]

    assert reco_ids == search_ids


def get_points(collection_name, ids: list):
    response = request_with_validation(
        api="/collections/{collection_name}/points",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            "ids": ids,
            "with_vector": True,
        },
    )
    assert response.ok
    return response.json()["result"]


def test_raw_vectors(collection_name):
    points = get_points(collection_name, [1, 2, 3, 4, 5, 6, 7, 8])

    # Assert using ids is the same as using the raw vectors
    response_ids = request_with_validation(
        api="/collections/{collection_name}/points/recommend",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            "positive": [point["id"] for point in points[:2]],
            "negative": [point["id"] for point in points[2:4]],
            "limit": 8,
        },
    )
    assert response_ids.ok
    assert len(response_ids.json()["result"]) == 4

    response_raw = request_with_validation(
        api="/collections/{collection_name}/points/recommend",
        method="POST",
        path_params={"collection_name": collection_name},
        body={
            "positive": [point["vector"] for point in points[:2]],
            "negative": [point["vector"] for point in points[2:4]],
            "limit": 8,
            "filter": {
                "must_not": [
                    {
                        # simulate using ids behavior
                        "has_id": [point["id"] for point in points[:4]]
                    }
                ]
            },
        },
    )
    assert response_raw.ok
    assert len(response_raw.json()["result"]) == 4

    assert response_ids.json()["result"] == response_raw.json()["result"]
