#!/usr/bin/env python
"""
    A py.test plugin which helps testing Flask applications.

    :copyright: (c) by Vital Kudzelka
    :license: MIT
"""
import sys

import pytest
from flask import json
from werkzeug.utils import cached_property

from .fixtures import accept_any
from .fixtures import accept_json
from .fixtures import accept_jsonp
from .fixtures import accept_mimetype
from .fixtures import client
from .fixtures import client_class
from .fixtures import config
from .fixtures import live_server
from .fixtures import request_ctx
from .pytest_compat import getfixturevalue


class JSONResponse:
    """Mixin with testing helper methods for JSON responses."""

    def __eq__(self, other):
        if isinstance(other, int):
            return self.status_code == other
        return super().__eq__(other)

    def __ne__(self, other):
        return not self == other


def pytest_assertrepr_compare(op, left, right):
    if isinstance(left, JSONResponse) and op == "==" and isinstance(right, int):
        return [
            "Mismatch in status code for response: {} != {}".format(
                left.status_code,
                right,
            ),
            "Response status: {}".format(left.status),
        ]
    return None


def _make_test_response_class(response_class):
    """Extends the response class with special attribute to test JSON
    responses. Don't override user-defined `json` attribute if any.

    :param response_class: An original response class.
    """
    if "json" in response_class.__dict__:
        return response_class

    return type(str(JSONResponse), (response_class, JSONResponse), {})


@pytest.fixture(autouse=True)
def _monkeypatch_response_class(request, monkeypatch):
    """Set custom response class before test suite and restore the original
    after. Custom response has `json` property to easily test JSON responses::

        @app.route('/ping')
        def ping():
            return jsonify(ping='pong')

        def test_json(client):
            res = client.get(url_for('ping'))
            assert res.json == {'ping': 'pong'}

    """

    if "app" not in request.fixturenames:
        return

    app = getfixturevalue(request, "app")
    monkeypatch.setattr(
        app, "response_class", _make_test_response_class(app.response_class)
    )


@pytest.fixture(autouse=True)
def _push_request_context(request):
    """During tests execution request context has been pushed, e.g. `url_for`,
    `session`, etc. can be used in tests as is::

        def test_app(app, client):
            assert client.get(url_for('myview')).status_code == 200

    """
    if "app" not in request.fixturenames:
        return

    app = getfixturevalue(request, "app")

    # Get application bound to the live server if ``live_server`` fixture
    # is applied. Live server application has an explicit ``SERVER_NAME``,
    # so ``url_for`` function generates a complete URL for endpoint which
    # includes application port as well.
    if "live_server" in request.fixturenames:
        app = getfixturevalue(request, "live_server").app

    ctx = app.test_request_context()
    ctx.push()

    def teardown():
        ctx.pop()

    request.addfinalizer(teardown)


@pytest.fixture(autouse=True)
def _configure_application(request, monkeypatch):
    """Use `pytest.mark.options` decorator to pass options to your application
    factory::

        @pytest.mark.options(debug=False)
        def test_something(app):
            assert not app.debug, 'the application works not in debug mode!'

    """
    if "app" not in request.fixturenames:
        return

    app = getfixturevalue(request, "app")
    for options in request.node.iter_markers("options"):
        for key, value in options.kwargs.items():
            monkeypatch.setitem(app.config, key.upper(), value)


def pytest_addoption(parser):
    group = parser.getgroup("flask")
    group.addoption(
        "--start-live-server",
        action="store_true",
        dest="start_live_server",
        default=True,
        help="start server automatically when live_server "
        "fixture is applied (enabled by default).",
    )
    group.addoption(
        "--no-start-live-server",
        action="store_false",
        dest="start_live_server",
        help="don't start server automatically when live_server " "fixture is applied.",
    )
    group.addoption(
        "--live-server-wait",
        action="store",
        dest="live_server_wait",
        default=5,
        type=float,
        help="the timeout after which test case is aborted if live server is "
        " not started.",
    )
    group.addoption(
        "--live-server-clean-stop",
        action="store_true",
        dest="live_server_clean_stop",
        default=True,
        help="attempt to kill the live server cleanly.",
    )
    group.addoption(
        "--no-live-server-clean-stop",
        action="store_false",
        dest="live_server_clean_stop",
        help="terminate the server forcefully after stop.",
    )
    group.addoption(
        "--live-server-host",
        action="store",
        default="localhost",
        type=str,
        help="use a host where to listen (default localhost).",
    )
    group.addoption(
        "--live-server-port",
        action="store",
        default=0,
        type=int,
        help="use a fixed port for the live_server fixture.",
    )
    parser.addini(
        "live_server_scope",
        "modify the scope of the live_server fixture.",
        default="session",
    )


def pytest_configure(config):
    config.addinivalue_line(
        "markers", "app(options): pass options to your application factory"
    )
    config.addinivalue_line("markers", "options: app config manipulation")
