import json

from os.path import join, dirname

from jsonschema import validate
from werkzeug.datastructures import FileStorage

import flask_restx as restx

from urllib.parse import parse_qs, urlparse


with open(join(dirname(__file__), "postman-v1.schema.json")) as f:
    schema = json.load(f)


class PostmanTest(object):
    def test_basic_export(self, app):
        api = restx.Api(app)

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 0

    def test_export_infos(self, app):
        api = restx.Api(
            app,
            version="1.0",
            title="My API",
            description="This is a testing API",
        )

        data = api.as_postman()

        validate(data, schema)

        assert data["name"] == "My API 1.0"
        assert data["description"] == "This is a testing API"

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

        @api.route("/test")
        class Test(restx.Resource):
            @api.doc("test_post")
            def post(self):
                """A test post"""
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["name"] == "test_post"
        assert request["description"] == "A test post"

        assert len(data["folders"]) == 1
        folder = data["folders"][0]
        assert folder["name"] == "default"
        assert folder["description"] == "Default namespace"

        assert request["folder"] == folder["id"]

    def test_export_with_namespace(self, app):
        api = restx.Api(app)
        ns = api.namespace("test", "A test namespace")

        @ns.route("/test")
        class Test(restx.Resource):
            @api.doc("test_post")
            def post(self):
                """A test post"""
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["name"] == "test_post"
        assert request["description"] == "A test post"

        assert len(data["folders"]) == 1
        folder = data["folders"][0]
        assert folder["name"] == "test"
        assert folder["description"] == "A test namespace"

        assert request["folder"] == folder["id"]

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

        first = api.as_postman()

        second = api.as_postman()

        assert first["id"] == second["id"]

    def test_resources_order_in_folder(self, app):
        """It should preserve resources order"""
        api = restx.Api(app)
        ns = api.namespace("test", "A test namespace")

        @ns.route("/test1")
        class Test1(restx.Resource):
            @api.doc("test_post_z")
            def post(self):
                pass

        @ns.route("/test2")
        class Test2(restx.Resource):
            @api.doc("test_post_y")
            def post(self):
                pass

        @ns.route("/test3")
        class Test3(restx.Resource):
            @api.doc("test_post_x")
            def post(self):
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 3

        assert len(data["folders"]) == 1
        folder = data["folders"][0]
        assert folder["name"] == "test"

        expected_order = ("test_post_z", "test_post_y", "test_post_x")
        assert len(folder["order"]) == len(expected_order)

        for request_id, expected in zip(folder["order"], expected_order):
            request = list(filter(lambda r: r["id"] == request_id, data["requests"]))[0]
            assert request["name"] == expected

    def test_prefix_with_trailing_slash(self, app):
        api = restx.Api(app, prefix="/prefix/")

        @api.route("/test/")
        class Test(restx.Resource):
            @api.doc("test_post")
            def post(self):
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["url"] == "http://localhost/prefix/test/"

    def test_prefix_without_trailing_slash(self, app):
        api = restx.Api(app, prefix="/prefix")

        @api.route("/test/")
        class Test(restx.Resource):
            @api.doc("test_post")
            def post(self):
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["url"] == "http://localhost/prefix/test/"

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

        @api.route("/test/<id>/<int:integer>/<float:number>/")
        class Test(restx.Resource):
            @api.doc("test_post")
            def post(self):
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["url"] == "http://localhost/test/:id/:integer/:number/"
        assert request["pathVariables"] == {
            "id": "",
            "integer": 0,
            "number": 0,
        }

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

        parser = api.parser()
        parser.add_argument("int", type=int)
        parser.add_argument("default", type=int, default=5)
        parser.add_argument("str", type=str)

        @api.route("/test/")
        class Test(restx.Resource):
            @api.expect(parser)
            def get(self):
                pass

        data = api.as_postman()

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["url"] == "http://localhost/test/"

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

        parser = api.parser()
        parser.add_argument("int", type=int)
        parser.add_argument("default", type=int, default=5)
        parser.add_argument("str", type=str)

        @api.route("/test/")
        class Test(restx.Resource):
            @api.expect(parser)
            def get(self):
                pass

        data = api.as_postman(urlvars=True)

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        qs = parse_qs(urlparse(request["url"]).query, keep_blank_values=True)

        assert "int" in qs
        assert qs["int"][0] == "0"

        assert "default" in qs
        assert qs["default"][0] == "5"

        assert "str" in qs
        assert qs["str"][0] == ""

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

        parser = api.parser()
        parser.add_argument("X-Header-1", location="headers", default="xxx")
        parser.add_argument("X-Header-2", location="headers", required=True)

        @api.route("/headers/")
        class TestHeaders(restx.Resource):
            @api.doc("headers")
            @api.expect(parser)
            def get(self):
                pass

        data = api.as_postman(urlvars=True)

        validate(data, schema)
        request = data["requests"][0]
        headers = dict(r.split(":") for r in request["headers"].splitlines())

        assert headers["X-Header-1"] == "xxx"
        assert headers["X-Header-2"] == ""

    def test_content_type_header(self, app):
        api = restx.Api(app)
        form_parser = api.parser()
        form_parser.add_argument("param", type=int, help="Some param", location="form")

        file_parser = api.parser()
        file_parser.add_argument("in_files", type=FileStorage, location="files")

        @api.route("/json/")
        class TestJson(restx.Resource):
            @api.doc("json")
            def post(self):
                pass

        @api.route("/form/")
        class TestForm(restx.Resource):
            @api.doc("form")
            @api.expect(form_parser)
            def post(self):
                pass

        @api.route("/file/")
        class TestFile(restx.Resource):
            @api.doc("file")
            @api.expect(file_parser)
            def post(self):
                pass

        @api.route("/get/")
        class TestGet(restx.Resource):
            @api.doc("get")
            def get(self):
                pass

        data = api.as_postman(urlvars=True)

        validate(data, schema)
        requests = dict((r["name"], r["headers"]) for r in data["requests"])

        assert requests["json"] == "Content-Type:application/json"
        assert requests["form"] == "Content-Type:multipart/form-data"
        assert requests["file"] == "Content-Type:multipart/form-data"

        # No content-type on get
        assert requests["get"] == ""

    def test_method_security_headers(self, app):
        api = restx.Api(
            app,
            authorizations={
                "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
            },
        )

        @api.route("/secure/")
        class Secure(restx.Resource):
            @api.doc("secure", security="apikey")
            def get(self):
                pass

        @api.route("/unsecure/")
        class Unsecure(restx.Resource):
            @api.doc("unsecure")
            def get(self):
                pass

        data = api.as_postman()

        validate(data, schema)
        requests = dict((r["name"], r["headers"]) for r in data["requests"])

        assert requests["unsecure"] == ""
        assert requests["secure"] == "X-API:"

    def test_global_security_headers(self, app):
        api = restx.Api(
            app,
            security="apikey",
            authorizations={
                "apikey": {"type": "apiKey", "in": "header", "name": "X-API"}
            },
        )

        @api.route("/test/")
        class Test(restx.Resource):
            def get(self):
                pass

        data = api.as_postman()

        validate(data, schema)
        request = data["requests"][0]
        headers = dict(r.split(":") for r in request["headers"].splitlines())

        assert headers["X-API"] == ""

    def test_oauth_security_headers(self, app):
        api = restx.Api(
            app,
            security="oauth",
            authorizations={
                "oauth": {
                    "type": "oauth2",
                    "authorizationUrl": "https://somewhere.com/oauth/authorize",
                    "flow": "implicit",
                    "scopes": {"read": "Can read", "write": "Can write"},
                }
            },
        )

        @api.route("/test/")
        class Test(restx.Resource):
            def get(self):
                pass

        data = api.as_postman()

        validate(data, schema)
        # request = data['requests'][0]
        # headers = dict(r.split(':') for r in request['headers'].splitlines())
        #
        # assert headers['X-API'] == ''

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

        data = api.as_postman(swagger=True)

        validate(data, schema)

        assert len(data["requests"]) == 1
        request = data["requests"][0]
        assert request["name"] == "Swagger specifications"
        assert request["description"] == "The API Swagger specifications as JSON"
        assert request["url"] == "http://localhost/swagger.json"
