"""
Example Code for a test case for the documentation site
=======================================================

**Module:** ``tests.test_docs_example``

This is the test code for the documentation site's :doc:`User guide </pages/guide>`. It contains everything that's
needed to follow the example -- and makes sure the code example is working.
"""
from functools import reduce
from os.path import dirname, join
from typing import List, Tuple, Union

import pytest

from pytest_csv_params.decorator import csv_params


def get_smallest_possible_container(
    number_of_items: int,
    dimensions_of_item: Tuple[int, int, int],
    available_container_sizes: Union[List[int], Tuple[int, ...]] = (1000, 2500, 7500),
) -> int:
    """
    This is the method to be tested. It searches for the smallest possible container after calculating the volume of the
    things to be loaded into the container. A container can only contain items of one product, so it is enough to know
    about the size of a single product, and how many of them need to be stored in a container.

    The method raises a :class:`ValueError` when the items do not fit any container.

    :param number_of_items: Number of items to be packed
    :param dimensions_of_item: Edge lengths of a single item
    :param available_container_sizes: What container sizes are available? This parameter has a default value
                                      ``(1000, 2500, 7500)``.
    :raises ValueError: When no matching container can be found
    """
    volume = reduce(lambda x, y: x * y, [*dimensions_of_item, number_of_items])
    possible_containers = list(filter(lambda s: s >= volume, available_container_sizes))
    if len(possible_containers) == 0:
        raise ValueError("No container available") from None
    return min(possible_containers)


def get_dimensions(dimensions_str: str) -> Tuple[int, int, int]:
    """
    Read the dimensions from a string. A helper method to build the dimensions tuple.

    :param dimensions_str: The dimensions from the CSV file
    :raises ValueError: When the dimensions cannot be converted
    :returns: The dimensions as int tuple
    """
    dimensions_tuple = tuple(map(lambda x: int(x.strip()), dimensions_str.split("x")))
    if len(dimensions_tuple) != 3:
        raise ValueError("Dimensions invalid") from None
    return dimensions_tuple  # type: ignore


@csv_params(
    data_file=join(dirname(__file__), "assets", "doc-example.csv"),
    id_col="Test-ID",
    header_renames={
        "Number of items": "number_of_items",
        "Dimensions of item": "dimensions_of_item",
        "Expected Container Size": "expected_container_size",
        "Expect Exception?": "expect_exception",
        "Expected Message": "expected_message",
    },
    data_casts={
        "number_of_items": int,
        "dimensions_of_item": get_dimensions,
        "expected_container_size": int,
        "expect_exception": lambda x: x == "Y",
    },
)
def test_get_smallest_possible_container(
    number_of_items: int,
    dimensions_of_item: Tuple[int, int, int],
    expected_container_size: int,
    expect_exception: bool,
    expected_message: str,
) -> None:
    """
    This is the test method for the documentation.

    :param number_of_items: The number of items
    :param dimensions_of_item: The dimensions of a single item
    :param expected_container_size: Expected container size
    :param expect_exception: Expect a :class:`ValueError`
    :param expected_message: Message of the exception
    """
    if expect_exception:
        with pytest.raises(ValueError) as expected_exception:
            get_smallest_possible_container(number_of_items, dimensions_of_item)
        assert expected_exception.value.args[0] == expected_message
    else:
        container_size = get_smallest_possible_container(number_of_items, dimensions_of_item)
        assert container_size == expected_container_size
