import logging

from asyncio import CancelledError, sleep
from itertools import count

from sanic.exceptions import NotFound
from sanic.request import Request
from sanic.response import HTTPResponse, json, text


# ------------------------------------------------------------ #
#  GET
# ------------------------------------------------------------ #


def test_middleware_request(app):
    results = []

    @app.middleware
    async def handler1(request):
        results.append(request)

    @app.route("/")
    async def handler2(request):
        return text("OK")

    request, response = app.test_client.get("/")

    assert response.text == "OK"
    assert type(results[0]) is Request


def test_middleware_request_as_convenience(app):
    results = []

    @app.on_request
    async def handler1(request):
        results.append(request)

    @app.on_request()
    async def handler2(request):
        results.append(request)

    @app.route("/")
    async def handler3(request):
        return text("OK")

    request, response = app.test_client.get("/")

    assert response.text == "OK"
    assert type(results[0]) is Request
    assert type(results[1]) is Request


def test_middleware_response(app):
    results = []

    @app.middleware("request")
    async def process_request(request):
        results.append(request)

    @app.middleware("response")
    async def process_response(request, response):
        results.append(request)
        results.append(response)

    @app.route("/")
    async def handler(request):
        return text("OK")

    request, response = app.test_client.get("/")

    assert response.text == "OK"
    assert type(results[0]) is Request
    assert type(results[1]) is Request
    assert isinstance(results[2], HTTPResponse)


def test_middleware_response_as_convenience(app):
    results = []

    @app.on_request
    async def process_request(request):
        results.append(request)

    @app.on_response
    async def process_response_1(request, response):
        results.append(request)
        results.append(response)

    @app.on_response()
    async def process_response_2(request, response):
        results.append(request)
        results.append(response)

    @app.route("/")
    async def handler(request):
        return text("OK")

    request, response = app.test_client.get("/")

    assert response.text == "OK"
    assert type(results[0]) is Request
    assert type(results[1]) is Request
    assert isinstance(results[2], HTTPResponse)
    assert type(results[3]) is Request
    assert isinstance(results[4], HTTPResponse)


def test_middleware_response_as_convenience_called(app):
    results = []

    @app.on_request()
    async def process_request(request):
        results.append(request)

    @app.on_response()
    async def process_response(request, response):
        results.append(request)
        results.append(response)

    @app.route("/")
    async def handler(request):
        return text("OK")

    request, response = app.test_client.get("/")

    assert response.text == "OK"
    assert type(results[0]) is Request
    assert type(results[1]) is Request
    assert isinstance(results[2], HTTPResponse)


def test_middleware_response_exception(app):
    result = {"status_code": "middleware not run"}

    @app.middleware("response")
    async def process_response(request, response):
        result["status_code"] = response.status
        return response

    @app.exception(NotFound)
    async def error_handler(request, exception):
        return text("OK", exception.status_code)

    @app.route("/")
    async def handler(request):
        return text("FAIL")

    request, response = app.test_client.get("/page_not_found")
    assert response.text == "OK"
    assert result["status_code"] == 404


def test_middleware_response_raise_cancelled_error(app, caplog):
    app.config.RESPONSE_TIMEOUT = 1

    @app.middleware("response")
    async def process_response(request, response):
        raise CancelledError("CancelledError at response middleware")

    @app.get("/")
    def handler(request):
        return text("OK")

    with caplog.at_level(logging.ERROR):
        reqrequest, response = app.test_client.get("/")

        assert response.status == 500
        assert (
            "sanic.error",
            logging.ERROR,
            "Exception occurred while handling uri: 'http://127.0.0.1:42101/'",
        ) not in caplog.record_tuples


def test_middleware_response_raise_exception(app, caplog):
    @app.middleware("response")
    async def process_response(request, response):
        raise Exception("Exception at response middleware")

    app.route("/")(lambda x: x)
    with caplog.at_level(logging.ERROR):
        reqrequest, response = app.test_client.get("/fail")

    assert response.status == 404
    # 404 errors are not logged
    assert (
        "sanic.error",
        logging.ERROR,
        "Exception occurred while handling uri: 'http://127.0.0.1:42101/'",
    ) not in caplog.record_tuples
    # Middleware exception ignored but logged
    assert (
        "sanic.error",
        logging.ERROR,
        "Exception occurred in one of response middleware handlers",
    ) in caplog.record_tuples


def test_middleware_override_request(app):
    @app.middleware
    async def halt_request(request):
        return text("OK")

    @app.route("/")
    async def handler(request):
        return text("FAIL")

    _, response = app.test_client.get("/", gather_request=False)

    assert response.status == 200
    assert response.text == "OK"


def test_middleware_override_response(app):
    @app.middleware("response")
    async def process_response(request, response):
        return text("OK")

    @app.route("/")
    async def handler(request):
        return text("FAIL")

    request, response = app.test_client.get("/")

    assert response.status == 200
    assert response.text == "OK"


def test_middleware_order(app):
    order = []

    @app.middleware("request")
    async def request1(request):
        order.append(1)

    @app.middleware("request")
    async def request2(request):
        order.append(2)

    @app.middleware("request")
    async def request3(request):
        order.append(3)

    @app.middleware("response")
    async def response1(request, response):
        order.append(6)

    @app.middleware("response")
    async def response2(request, response):
        order.append(5)

    @app.middleware("response")
    async def response3(request, response):
        order.append(4)

    @app.route("/")
    async def handler(request):
        return text("OK")

    request, response = app.test_client.get("/")

    assert response.status == 200
    assert order == [1, 2, 3, 4, 5, 6]


def test_request_middleware_executes_once(app):
    i = count()

    @app.middleware("request")
    async def inc(request):
        nonlocal i
        next(i)

    @app.route("/")
    async def handler(request):
        await request.app._run_request_middleware(request)
        return text("OK")

    request, response = app.test_client.get("/")
    assert next(i) == 1

    request, response = app.test_client.get("/")
    assert next(i) == 3


def test_middleware_added_response(app):
    @app.on_response
    def display(_, response):
        response["foo"] = "bar"
        return json(response)

    @app.get("/")
    async def handler(request):
        return {}

    _, response = app.test_client.get("/")
    assert response.json["foo"] == "bar"


def test_middleware_return_response(app):
    response_middleware_run_count = 0
    request_middleware_run_count = 0

    @app.on_response
    def response(_, response):
        nonlocal response_middleware_run_count
        response_middleware_run_count += 1

    @app.on_request
    def request(_):
        nonlocal request_middleware_run_count
        request_middleware_run_count += 1

    @app.get("/")
    async def handler(request):
        resp1 = await request.respond()
        return resp1

    app.test_client.get("/")
    assert response_middleware_run_count == 1
    assert request_middleware_run_count == 1


def test_middleware_run_on_timeout(app):
    app.config.RESPONSE_TIMEOUT = 0.1
    response_middleware_run_count = 0
    request_middleware_run_count = 0

    @app.on_response
    def response(_, response):
        nonlocal response_middleware_run_count
        response_middleware_run_count += 1

    @app.on_request
    def request(_):
        nonlocal request_middleware_run_count
        request_middleware_run_count += 1

    @app.get("/")
    async def handler(request):
        resp1 = await request.respond()
        await sleep(1)
        return resp1

    app.test_client.get("/")
    assert request_middleware_run_count == 1
    assert response_middleware_run_count == 1
