import copy

from flask import url_for, Blueprint

import flask_restx as restx


class APITest(object):
    def test_root_endpoint(self, app):
        api = restx.Api(app, version="1.0")

        with app.test_request_context():
            url = url_for("root")
            assert url == "/"
            assert api.base_url == "http://localhost/"

    def test_root_endpoint_lazy(self, app):
        api = restx.Api(version="1.0")
        api.init_app(app)

        with app.test_request_context():
            url = url_for("root")
            assert url == "/"
            assert api.base_url == "http://localhost/"

    def test_root_endpoint_with_blueprint(self, app):
        blueprint = Blueprint("api", __name__, url_prefix="/api")
        api = restx.Api(blueprint, version="1.0")
        app.register_blueprint(blueprint)

        with app.test_request_context():
            url = url_for("api.root")
            assert url == "/api/"
            assert api.base_url == "http://localhost/api/"

    def test_root_endpoint_with_blueprint_with_subdomain(self, app):
        blueprint = Blueprint("api", __name__, subdomain="api", url_prefix="/api")
        api = restx.Api(blueprint, version="1.0")
        app.register_blueprint(blueprint)

        with app.test_request_context():
            url = url_for("api.root")
            assert url == "http://api.localhost/api/"
            assert api.base_url == "http://api.localhost/api/"

    def test_parser(self):
        api = restx.Api()
        assert isinstance(api.parser(), restx.reqparse.RequestParser)

    def test_doc_decorator(self, app):
        api = restx.Api(app, prefix="/api", version="1.0")
        params = {"q": {"description": "some description"}}

        @api.doc(params=params)
        class TestResource(restx.Resource):
            pass

        assert hasattr(TestResource, "__apidoc__")
        assert TestResource.__apidoc__ == {"params": params}

    def test_doc_with_inheritance(self, app):
        api = restx.Api(app, prefix="/api", version="1.0")
        base_params = {
            "q": {
                "description": "some description",
                "type": "string",
                "paramType": "query",
            }
        }
        child_params = {
            "q": {"description": "some new description"},
            "other": {"description": "another param"},
        }

        @api.doc(params=base_params)
        class BaseResource(restx.Resource):
            pass

        @api.doc(params=child_params)
        class TestResource(BaseResource):
            pass

        assert TestResource.__apidoc__ == {
            "params": {
                "q": {
                    "description": "some new description",
                    "type": "string",
                    "paramType": "query",
                },
                "other": {"description": "another param"},
            }
        }

    def test_specs_endpoint_not_added(self, app):
        api = restx.Api()
        api.init_app(app, add_specs=False)
        assert "specs" not in api.endpoints
        assert "specs" not in app.view_functions

    def test_specs_endpoint_not_found_if_not_added(self, app, client):
        api = restx.Api()
        api.init_app(app, add_specs=False)
        resp = client.get("/swagger.json")
        assert resp.status_code == 404

    def test_default_endpoint(self, app):
        api = restx.Api(app)

        @api.route("/test/")
        class TestResource(restx.Resource):
            pass

        with app.test_request_context():
            assert url_for("test_resource") == "/test/"

    def test_default_endpoint_lazy(self, app):
        api = restx.Api()

        @api.route("/test/")
        class TestResource(restx.Resource):
            pass

        api.init_app(app)

        with app.test_request_context():
            assert url_for("test_resource") == "/test/"

    def test_default_endpoint_with_blueprint(self, app):
        blueprint = Blueprint("api", __name__, url_prefix="/api")
        api = restx.Api(blueprint)
        app.register_blueprint(blueprint)

        @api.route("/test/")
        class TestResource(restx.Resource):
            pass

        with app.test_request_context():
            assert url_for("api.test_resource") == "/api/test/"

    def test_default_endpoint_with_blueprint_with_subdomain(self, app):
        blueprint = Blueprint("api", __name__, subdomain="api", url_prefix="/api")
        api = restx.Api(blueprint)
        app.register_blueprint(blueprint)

        @api.route("/test/")
        class TestResource(restx.Resource):
            pass

        with app.test_request_context():
            assert url_for("api.test_resource") == "http://api.localhost/api/test/"

    def test_default_endpoint_for_namespace(self, app):
        api = restx.Api(app)
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test/")
        class TestResource(restx.Resource):
            pass

        with app.test_request_context():
            assert url_for("ns_test_resource") == "/ns/test/"

    def test_default_endpoint_lazy_for_namespace(self, app):
        api = restx.Api()
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test/")
        class TestResource(restx.Resource):
            pass

        api.init_app(app)

        with app.test_request_context():
            assert url_for("ns_test_resource") == "/ns/test/"

    def test_default_endpoint_for_namespace_with_blueprint(self, app):
        blueprint = Blueprint("api", __name__, url_prefix="/api")
        api = restx.Api(blueprint)
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test/")
        class TestResource(restx.Resource):
            pass

        app.register_blueprint(blueprint)

        with app.test_request_context():
            assert url_for("api.ns_test_resource") == "/api/ns/test/"

    def test_multiple_default_endpoint(self, app):
        api = restx.Api(app)

        @api.route("/test2/")
        @api.route("/test/")
        class TestResource(restx.Resource):
            pass

        with app.test_request_context():
            assert url_for("test_resource") == "/test/"
            assert url_for("test_resource_2") == "/test2/"

    def test_multiple_default_endpoint_lazy(self, app):
        api = restx.Api()

        @api.route("/test2/")
        @api.route("/test/")
        class TestResource(restx.Resource):
            pass

        api.init_app(app)

        with app.test_request_context():
            assert url_for("test_resource") == "/test/"
            assert url_for("test_resource_2") == "/test2/"

    def test_multiple_default_endpoint_for_namespace(self, app):
        api = restx.Api(app)
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test2/")
        @ns.route("/test/")
        class TestResource(restx.Resource):
            pass

        with app.test_request_context():
            assert url_for("ns_test_resource") == "/ns/test/"
            assert url_for("ns_test_resource_2") == "/ns/test2/"

    def test_multiple_default_endpoint_lazy_for_namespace(self, app):
        api = restx.Api()
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test2/")
        @ns.route("/test/")
        class TestResource(restx.Resource):
            pass

        api.init_app(app)

        with app.test_request_context():
            assert url_for("ns_test_resource") == "/ns/test/"
            assert url_for("ns_test_resource_2") == "/ns/test2/"

    def test_multiple_default_endpoint_for_namespace_with_blueprint(self, app):
        blueprint = Blueprint("api", __name__, url_prefix="/api")
        api = restx.Api(blueprint)
        ns = api.namespace("ns", "Test namespace")

        @ns.route("/test2/")
        @ns.route("/test/")
        class TestResource(restx.Resource):
            pass

        app.register_blueprint(blueprint)

        with app.test_request_context():
            assert url_for("api.ns_test_resource") == "/api/ns/test/"
            assert url_for("api.ns_test_resource_2") == "/api/ns/test2/"

    def test_ns_path_prefixes(self, app):
        api = restx.Api()
        ns = restx.Namespace("test_ns", description="Test namespace")

        @ns.route("/test/", endpoint="test_resource")
        class TestResource(restx.Resource):
            pass

        api.add_namespace(ns, "/api_test")
        api.init_app(app)

        with app.test_request_context():
            assert url_for("test_resource") == "/api_test/test/"

    def test_multiple_ns_with_authorizations(self, app):
        api = restx.Api()
        a1 = {"apikey": {"type": "apiKey", "in": "header", "name": "X-API"}}
        a2 = {
            "oauth2": {
                "type": "oauth2",
                "flow": "accessCode",
                "tokenUrl": "https://somewhere.com/token",
                "scopes": {
                    "read": "Grant read-only access",
                    "write": "Grant read-write access",
                },
            }
        }
        ns1 = restx.Namespace("ns1", authorizations=a1)
        ns2 = restx.Namespace("ns2", authorizations=a2)

        @ns1.route("/")
        class Ns1(restx.Resource):
            @ns1.doc(security="apikey")
            def get(self):
                pass

        @ns2.route("/")
        class Ns2(restx.Resource):
            @ns1.doc(security="oauth2")
            def post(self):
                pass

        api.add_namespace(ns1, path="/ns1")
        api.add_namespace(ns2, path="/ns2")
        api.init_app(app)

        assert {"apikey": []} in api.__schema__["paths"]["/ns1/"]["get"]["security"]
        assert {"oauth2": []} in api.__schema__["paths"]["/ns2/"]["post"]["security"]
        unified_auth = copy.copy(a1)
        unified_auth.update(a2)
        assert api.__schema__["securityDefinitions"] == unified_auth

    def test_non_ordered_namespace(self, app):
        api = restx.Api(app)
        ns = api.namespace("ns", "Test namespace")

        assert not ns.ordered

    def test_ordered_namespace(self, app):
        api = restx.Api(app, ordered=True)
        ns = api.namespace("ns", "Test namespace")

        assert ns.ordered

    def test_decorators(self, app, mocker):
        decorator1 = mocker.Mock(return_value=lambda x: x)
        decorator2 = mocker.Mock(return_value=lambda x: x)
        decorator3 = mocker.Mock(return_value=lambda x: x)

        class TestResource(restx.Resource):
            method_decorators = []

        api = restx.Api(decorators=[decorator1])
        ns = api.namespace("test_ns", decorators=[decorator2, decorator3])

        ns.add_resource(TestResource, "/test", endpoint="test")
        api.init_app(app)

        assert decorator1.called is True
        assert decorator2.called is True
        assert decorator3.called is True

    def test_specs_url(self, app):
        api = restx.Api(app)
        specs_url = api.specs_url
        assert specs_url == "/swagger.json"

    def test_url_scheme(self, app):
        api = restx.Api(app, url_scheme="https")
        assert api.specs_url == "https://localhost/swagger.json"
        assert api.base_url == "https://localhost/"
