import sys
from collections.abc import Iterable, Sequence

import pytest

from returns.context import (
    NoDeps,
    ReaderFutureResult,
    ReaderIOResult,
    ReaderResult,
)
from returns.future import FutureFailure, FutureResult, FutureSuccess
from returns.io import IOFailure, IOSuccess
from returns.iterables import Fold
from returns.maybe import Nothing, Some
from returns.result import Failure, Success


@pytest.mark.parametrize(
    ('iterable', 'sequence'),
    [
        ([], Some(())),
        ([Some(1)], Some((1,))),
        ([Some(1), Some(2)], Some((1, 2))),
        ([Nothing, Some(1), Some(2)], Some((1, 2))),
        ([Some(1), Nothing, Some(2)], Some((1, 2))),
        ([Some(1), Some(2), Nothing], Some((1, 2))),
        ([Nothing], Some(())),
        ([], Success(())),
        ([Success(1)], Success((1,))),
        ([Success(1), Success(2)], Success((1, 2))),
        (
            [Failure('a'), Success(1), Success(2)],
            Success((1, 2)),
        ),
        ([Success(1), Failure('b')], Success((1,))),
        ([Failure('a'), Failure('b')], Success(())),
        ([], IOSuccess(())),
        ([IOSuccess(1)], IOSuccess((1,))),
        ([IOSuccess(1), IOSuccess(2)], IOSuccess((1, 2))),
        (
            [IOFailure('a'), IOSuccess(1), IOSuccess(2)],
            IOSuccess((1, 2)),
        ),
        ([IOSuccess(1), IOFailure('b')], IOSuccess((1,))),
        ([IOFailure('a'), IOFailure('b')], IOSuccess(())),
    ],
)
def test_collect_all_result(iterable, sequence):
    """Iterable for ``Result`` and ``Fold``."""
    assert Fold.collect_all(iterable, sequence.from_value(())) == sequence


@pytest.mark.parametrize(
    ('iterable', 'sequence'),
    [
        ([], ReaderResult.from_value(())),
        ([ReaderResult.from_value(1)], ReaderResult.from_value((1,))),
        (
            [ReaderResult.from_value(1), ReaderResult.from_value(2)],
            ReaderResult.from_value((1, 2)),
        ),
        (
            [
                ReaderResult.from_failure('a'),
                ReaderResult.from_value(1),
                ReaderResult.from_value(2),
            ],
            ReaderResult.from_value((1, 2)),
        ),
        (
            [ReaderResult.from_failure('a'), ReaderResult.from_failure('b')],
            ReaderResult.from_value(()),
        ),
        ([], ReaderIOResult.from_value(())),
        ([ReaderIOResult.from_value(1)], ReaderIOResult.from_value((1,))),
        (
            [ReaderIOResult.from_value(1), ReaderIOResult.from_value(2)],
            ReaderIOResult.from_value((1, 2)),
        ),
        (
            [
                ReaderIOResult.from_failure('a'),
                ReaderIOResult.from_value(1),
                ReaderIOResult.from_value(2),
            ],
            ReaderIOResult.from_value((1, 2)),
        ),
        (
            [
                ReaderIOResult.from_failure('a'),
                ReaderIOResult.from_failure('b'),
            ],
            ReaderIOResult.from_value(()),
        ),
    ],
)
def test_collect_all_reader_result(iterable, sequence):
    """Iterable for ``ReaderResult`` and ``Fold``."""
    assert Fold.collect_all(
        iterable,
        sequence.from_value(()),
    )(...) == sequence(...)


@pytest.mark.anyio
async def test_collect_all_reader_future_result(subtests):
    """Iterable for ``ReaderFutureResult`` and ``Fold``."""
    containers: list[
        tuple[  # noqa: WPS234
            Iterable[ReaderFutureResult[int, str, NoDeps]],
            ReaderFutureResult[Sequence[int], str, NoDeps],
        ]
    ] = [
        ([], ReaderFutureResult.from_value(())),
        (
            [ReaderFutureResult.from_value(1)],
            ReaderFutureResult.from_value((1,)),
        ),
        (
            [
                ReaderFutureResult.from_value(1),
                ReaderFutureResult.from_value(2),
            ],
            ReaderFutureResult.from_value((1, 2)),
        ),
        (
            [
                ReaderFutureResult.from_failure('a'),
                ReaderFutureResult.from_value(1),
                ReaderFutureResult.from_value(2),
            ],
            ReaderFutureResult.from_value((1, 2)),
        ),
        (
            [
                ReaderFutureResult.from_failure('a'),
                ReaderFutureResult.from_failure('b'),
            ],
            ReaderFutureResult.from_value(()),
        ),
    ]
    for iterable, sequence in containers:
        with subtests.test(iterable=iterable, sequence=sequence):
            assert await Fold.collect_all(
                iterable,
                sequence.from_value(()),
            )(...) == await sequence(...)


@pytest.mark.anyio
async def test_collect_all_future_result(subtests):
    """Iterable for ``FutureResult`` and ``Fold``."""
    containers: list[
        tuple[  # noqa: WPS234
            Iterable[FutureResult[int, str]],
            FutureResult[Sequence[int], str],
        ]
    ] = [
        ([], FutureSuccess(())),
        ([FutureSuccess(1)], FutureSuccess((1,))),
        ([FutureSuccess(1), FutureSuccess(2)], FutureSuccess((1, 2))),
        (
            [FutureFailure('a'), FutureSuccess(1), FutureSuccess(2)],
            FutureSuccess((1, 2)),
        ),
        ([FutureFailure('a'), FutureFailure('b')], FutureSuccess(())),
    ]
    for iterable, sequence in containers:
        with subtests.test(iterable=iterable, sequence=sequence):
            assert (
                await Fold.collect_all(
                    iterable,
                    sequence.from_value(()),
                )
                == await sequence
            )


def test_fold_collect_recursion_limit():
    """Ensures that ``.collect_all`` method is recursion safe."""
    limit = sys.getrecursionlimit() + 1
    iterable = (Success(1) for _ in range(limit))
    expected = Success((1,) * limit)
    assert Fold.collect_all(iterable, Success(())) == expected
