# -*G- coding: utf-8 -*-
import logging
import time

import pytest
import responses
from responses import matchers

from arkindex import ArkindexClient
from arkindex.pagination import PaginationMode


def test_pagination_empty(mock_schema):
    responses.add(
        responses.GET,
        "https://dummy.test/api/v1/elements/",
        json={"count": 0, "previous": None, "next": None, "results": []},
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    with pytest.raises(StopIteration):
        next(cli.paginate("ListElements"))


def test_page_mode_detection(mock_schema):
    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    pagination = cli.paginate("ListElements")
    responses.assert_call_count(
        count=1, url="https://dummy.test/api/v1/openapi/?format=json"
    )
    assert pagination.mode is PaginationMode.PageNumber


def test_cursor_mode_detection(mock_schema):
    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    pagination = cli.paginate("ListProcessElements", id="process_id")
    responses.assert_call_count(
        count=1, url="https://dummy.test/api/v1/openapi/?format=json"
    )
    assert pagination.mode is PaginationMode.Cursor


def test_page_pagination_with_missing_data(mock_schema, monkeypatch):
    """
    Page number pagination skips erroneous pages when allow_missing_data parameter is set
    """
    base_url = "https://dummy.test/api/v1/elements/"

    # Disable sleeps
    monkeypatch.setattr(time, "sleep", lambda x: None)

    # Page 1
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({})],
        json={
            "count": 9,
            "number": 1,
            "previous": None,
            "next": f"{base_url}?page=2",
            "results": [1, 2, 3],
        },
    )

    # Page 2 is erroneous
    # We need 3 responses, one for each retries
    for i in range(3):
        responses.add(
            responses.GET,
            base_url,
            match=[matchers.query_param_matcher({"page": 2})],
            status=400,
            json={"error": "some error happened"},
        )

    # Page 3
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"page": 3})],
        json={
            "count": 9,
            "number": 3,
            "previous": f"{base_url}?page=3",
            "next": None,
            "results": [7, 8, 9],
        },
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    paginator = cli.paginate("ListElements", allow_missing_data=True, retries=3)
    assert paginator.allow_missing_data is True

    # First page is retrieved
    assert len(paginator) == 9
    assert len(responses.calls) == 2
    responses.assert_call_count(count=1, url=base_url)
    # Remaining pages are retrieved with retries
    assert list(paginator) == [1, 2, 3, 7, 8, 9]
    assert len(responses.calls) == 6
    responses.assert_call_count(count=3, url=f"{base_url}?page=2")
    # Page 2 has failed
    assert paginator.missing == {2}


def test_page_pagination_incomplete(mock_schema, monkeypatch):
    """
    Pagination raises an exception on repeated errors by default
    """
    base_url = "https://dummy.test/api/v1/elements/"

    # Disable sleeps
    monkeypatch.setattr(time, "sleep", lambda x: None)

    # Page 1
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({})],
        json={
            "count": 9,
            "number": 1,
            "previous": None,
            "next": f"{base_url}?page=2",
            "results": ["A", "B"],
        },
    )

    # Page 2 is erroneous
    # Page 3 is not needed as it won't try to load it
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"page": 2})],
        status=500,
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    paginator = cli.paginate("ListElements", retries=1)
    assert paginator.allow_missing_data is False

    with pytest.raises(
        Exception, match="Stopping pagination as data will be incomplete"
    ):
        list(paginator)


def test_cursor_pagination_length(mock_schema):
    """
    The first page should be retrieved with the `with_count` parameter in case of a cursor pagination
    Allows the client to know the total number of results
    """
    base_url = "https://dummy.test/api/v1/process/process_id/elements/"

    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"with_count": "true"})],
        json={
            "count": 6,
            "previous": None,
            "next": f"{base_url}?cursor=DEF",
            "results": [1, 2, 3],
        },
    )
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"cursor": "DEF"})],
        json={
            "count": None,
            "previous": f"{base_url}?cursor=ABC",
            "next": None,
            "results": [4, 5, 6],
        },
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    paginator = cli.paginate("ListProcessElements", id="process_id")

    assert len(paginator) == 6
    assert len(responses.calls) == 2
    # Schema is used to detect the pagination mode
    responses.assert_call_count(
        count=1, url="https://dummy.test/api/v1/openapi/?format=json"
    )
    responses.assert_call_count(count=1, url=f"{base_url}?with_count=true")
    assert list(paginator) == list(range(1, 7))
    assert len(responses.calls) == 3


def test_cursor_pagination_zero_results(mock_schema):
    """
    Pagination should handle queries returning zero results
    """
    base_url = "https://dummy.test/api/v1/process/process_id/elements/"
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"with_count": "true"})],
        json={
            "count": 0,
            "previous": None,
            "next": None,
            "results": [],
        },
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    paginator = cli.paginate("ListProcessElements", id="process_id")

    assert len(paginator) == 0
    assert list(paginator) == []
    assert len(responses.calls) == 2


def test_cursor_pagination_len_errors(mock_schema, mocker):
    """
    Errors must be raised when calling the __len__ method of an iterator
    """
    base_url = "https://dummy.test/api/v1/process/process_id/elements/"
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"with_count": "true"})],
        status=500,
    )

    # Prevent waiting for a random amount of time between retries
    mocker.patch("arkindex.pagination.random.random", return_value=0.0001)

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    paginator = cli.paginate("ListProcessElements", id="process_id", retries=5)

    with pytest.raises(
        Exception, match="Stopping pagination as data will be incomplete"
    ):
        len(paginator)
    assert len(responses.calls) == 6


def test_paginate_x_paginated(mock_schema):
    responses.add(
        responses.GET,
        "https://dummy.test/api/v1/element/element_id/metadata/",
        match=[matchers.query_param_matcher({})],
        json=["a", "b"],
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    assert cli.paginate("ListElementMetaData", id="element_id") == ["a", "b"]
    assert len(responses.calls) == 2


def test_paginate_from_initial_page(mock_schema, caplog):
    caplog.set_level(logging.INFO)
    base_url = "https://dummy.test/api/v1/elements/"

    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"page": "2"})],
        json={
            "count": 9,
            "number": 1,
            "previous": f"{base_url}?page=1",
            "next": f"{base_url}?page=3",
            "results": [4, 5, 6],
        },
    )
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"page": "3"})],
        json={
            "count": 9,
            "number": 1,
            "previous": f"{base_url}?page=2",
            "next": None,
            "results": [7, 8, 9],
        },
    )

    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    assert list(cli.paginate("ListElements", page=2)) == [4, 5, 6, 7, 8, 9]
    assert len(responses.calls) == 3
    assert [(level, message) for _module, level, message in caplog.record_tuples] == [
        (logging.INFO, "Loading page 2 on try 1/5"),
        (logging.INFO, "Pagination will load a total of 2 pages."),
        (logging.INFO, "Loading page 3 on try 1/5 - remains 1 page to load."),
    ]


def test_paginate_from_initial_cursor(mock_schema, caplog):
    caplog.set_level(logging.INFO)
    base_url = "https://dummy.test/api/v1/process/process_id/elements/"

    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"cursor": "DEF", "with_count": "true"})],
        json={
            "count": 7,
            "previous": f"{base_url}?cursor=ABC",
            "next": f"{base_url}?cursor=GHI",
            "results": [4, 5, 6],
        },
    )
    responses.add(
        responses.GET,
        base_url,
        match=[matchers.query_param_matcher({"cursor": "GHI"})],
        json={
            "count": 7,
            "previous": f"{base_url}?cursor=ABC",
            "next": None,
            "results": [7],
        },
    )
    cli = ArkindexClient("t0k3n", base_url="https://dummy.test")
    assert list(cli.paginate("ListProcessElements", id="process_id", cursor="DEF")) == [
        4,
        5,
        6,
        7,
    ]
    assert len(responses.calls) == 3
    assert [(level, message) for _module, level, message in caplog.record_tuples] == [
        (logging.INFO, "Loading page DEF on try 1/5"),
        (logging.INFO, "Pagination will load a maximum of 3 pages"),
        (
            logging.INFO,
            "Loading cursor GHI on try 1/5 - remains a maximum of 2 pages to load.",
        ),
    ]
