import gzip
import json
import zlib
from io import BytesIO
from unittest import mock

from vcr.filters import (
    decode_response,
    remove_headers,
    remove_post_data_parameters,
    remove_query_parameters,
    replace_headers,
    replace_post_data_parameters,
    replace_query_parameters,
)
from vcr.request import Request


def test_replace_headers():
    # This tests all of:
    #   1. keeping a header
    #   2. removing a header
    #   3. replacing a header
    #   4. replacing a header using a callable
    #   5. removing a header using a callable
    #   6. replacing a header that doesn't exist
    headers = {"one": ["keep"], "two": ["lose"], "three": ["change"], "four": ["shout"], "five": ["whisper"]}
    request = Request("GET", "http://google.com", "", headers)
    replace_headers(
        request,
        [
            ("two", None),
            ("three", "tada"),
            ("four", lambda key, value, request: value.upper()),
            ("five", lambda key, value, request: None),
            ("six", "doesntexist"),
        ],
    )
    assert request.headers == {"one": "keep", "three": "tada", "four": "SHOUT"}


def test_replace_headers_empty():
    headers = {"hello": "goodbye", "secret": "header"}
    request = Request("GET", "http://google.com", "", headers)
    replace_headers(request, [])
    assert request.headers == headers


def test_replace_headers_callable():
    # This goes beyond test_replace_headers() to ensure that the callable
    # receives the expected arguments.
    headers = {"hey": "there"}
    request = Request("GET", "http://google.com", "", headers)
    callme = mock.Mock(return_value="ho")
    replace_headers(request, [("hey", callme)])
    assert request.headers == {"hey": "ho"}
    assert callme.call_args == ((), {"request": request, "key": "hey", "value": "there"})


def test_remove_headers():
    # Test the backward-compatible API wrapper.
    headers = {"hello": ["goodbye"], "secret": ["header"]}
    request = Request("GET", "http://google.com", "", headers)
    remove_headers(request, ["secret"])
    assert request.headers == {"hello": "goodbye"}


def test_replace_query_parameters():
    # This tests all of:
    #   1. keeping a parameter
    #   2. removing a parameter
    #   3. replacing a parameter
    #   4. replacing a parameter using a callable
    #   5. removing a parameter using a callable
    #   6. replacing a parameter that doesn't exist
    uri = "http://g.com/?one=keep&two=lose&three=change&four=shout&five=whisper"
    request = Request("GET", uri, "", {})
    replace_query_parameters(
        request,
        [
            ("two", None),
            ("three", "tada"),
            ("four", lambda key, value, request: value.upper()),
            ("five", lambda key, value, request: None),
            ("six", "doesntexist"),
        ],
    )
    assert request.query == [("four", "SHOUT"), ("one", "keep"), ("three", "tada")]


def test_remove_all_query_parameters():
    uri = "http://g.com/?q=cowboys&w=1"
    request = Request("GET", uri, "", {})
    replace_query_parameters(request, [("w", None), ("q", None)])
    assert request.uri == "http://g.com/"


def test_replace_query_parameters_callable():
    # This goes beyond test_replace_query_parameters() to ensure that the
    # callable receives the expected arguments.
    uri = "http://g.com/?hey=there"
    request = Request("GET", uri, "", {})
    callme = mock.Mock(return_value="ho")
    replace_query_parameters(request, [("hey", callme)])
    assert request.uri == "http://g.com/?hey=ho"
    assert callme.call_args == ((), {"request": request, "key": "hey", "value": "there"})


def test_remove_query_parameters():
    # Test the backward-compatible API wrapper.
    uri = "http://g.com/?q=cowboys&w=1"
    request = Request("GET", uri, "", {})
    remove_query_parameters(request, ["w"])
    assert request.uri == "http://g.com/?q=cowboys"


def test_replace_post_data_parameters():
    # This tests all of:
    #   1. keeping a parameter
    #   2. removing a parameter
    #   3. replacing a parameter
    #   4. replacing a parameter using a callable
    #   5. removing a parameter using a callable
    #   6. replacing a parameter that doesn't exist
    body = b"one=keep&two=lose&three=change&four=shout&five=whisper"
    request = Request("POST", "http://google.com", body, {})
    replace_post_data_parameters(
        request,
        [
            ("two", None),
            ("three", "tada"),
            ("four", lambda key, value, request: value.upper()),
            ("five", lambda key, value, request: None),
            ("six", "doesntexist"),
        ],
    )
    assert request.body == b"one=keep&three=tada&four=SHOUT"


def test_replace_post_data_parameters_empty_body():
    # This test ensures replace_post_data_parameters doesn't throw exception when body is empty.
    body = None
    request = Request("POST", "http://google.com", body, {})
    replace_post_data_parameters(
        request,
        [
            ("two", None),
            ("three", "tada"),
            ("four", lambda key, value, request: value.upper()),
            ("five", lambda key, value, request: None),
            ("six", "doesntexist"),
        ],
    )
    assert request.body is None


def test_remove_post_data_parameters():
    # Test the backward-compatible API wrapper.
    body = b"id=secret&foo=bar"
    request = Request("POST", "http://google.com", body, {})
    remove_post_data_parameters(request, ["id"])
    assert request.body == b"foo=bar"


def test_preserve_multiple_post_data_parameters():
    body = b"id=secret&foo=bar&foo=baz"
    request = Request("POST", "http://google.com", body, {})
    replace_post_data_parameters(request, [("id", None)])
    assert request.body == b"foo=bar&foo=baz"


def test_remove_all_post_data_parameters():
    body = b"id=secret&foo=bar"
    request = Request("POST", "http://google.com", body, {})
    replace_post_data_parameters(request, [("id", None), ("foo", None)])
    assert request.body == b""


def test_replace_json_post_data_parameters():
    # This tests all of:
    #   1. keeping a parameter
    #   2. removing a parameter
    #   3. replacing a parameter
    #   4. replacing a parameter using a callable
    #   5. removing a parameter using a callable
    #   6. replacing a parameter that doesn't exist
    body = b'{"one": "keep", "two": "lose", "three": "change", "four": "shout", "five": "whisper"}'
    request = Request("POST", "http://google.com", body, {})
    request.headers["Content-Type"] = "application/json"
    replace_post_data_parameters(
        request,
        [
            ("two", None),
            ("three", "tada"),
            ("four", lambda key, value, request: value.upper()),
            ("five", lambda key, value, request: None),
            ("six", "doesntexist"),
        ],
    )
    request_data = json.loads(request.body)
    expected_data = json.loads('{"one": "keep", "three": "tada", "four": "SHOUT"}')
    assert request_data == expected_data


def test_remove_json_post_data_parameters():
    # Test the backward-compatible API wrapper.
    body = b'{"id": "secret", "foo": "bar", "baz": "qux"}'
    request = Request("POST", "http://google.com", body, {})
    request.headers["Content-Type"] = "application/json"
    remove_post_data_parameters(request, ["id"])
    request_body_json = json.loads(request.body)
    expected_json = json.loads(b'{"foo": "bar", "baz": "qux"}')
    assert request_body_json == expected_json


def test_remove_all_json_post_data_parameters():
    body = b'{"id": "secret", "foo": "bar"}'
    request = Request("POST", "http://google.com", body, {})
    request.headers["Content-Type"] = "application/json"
    replace_post_data_parameters(request, [("id", None), ("foo", None)])
    assert request.body == b"{}"


def test_replace_dict_post_data_parameters():
    # This tests all of:
    #   1. keeping a parameter
    #   2. removing a parameter
    #   3. replacing a parameter
    #   4. replacing a parameter using a callable
    #   5. removing a parameter using a callable
    #   6. replacing a parameter that doesn't exist
    body = {"one": "keep", "two": "lose", "three": "change", "four": "shout", "five": "whisper"}
    request = Request("POST", "http://google.com", body, {})
    request.headers["Content-Type"] = "application/x-www-form-urlencoded"
    replace_post_data_parameters(
        request,
        [
            ("two", None),
            ("three", "tada"),
            ("four", lambda key, value, request: value.upper()),
            ("five", lambda key, value, request: None),
            ("six", "doesntexist"),
        ],
    )
    expected_data = {"one": "keep", "three": "tada", "four": "SHOUT"}
    assert request.body == expected_data


def test_remove_dict_post_data_parameters():
    # Test the backward-compatible API wrapper.
    body = {"id": "secret", "foo": "bar", "baz": "qux"}
    request = Request("POST", "http://google.com", body, {})
    request.headers["Content-Type"] = "application/x-www-form-urlencoded"
    remove_post_data_parameters(request, ["id"])
    expected_data = {"foo": "bar", "baz": "qux"}
    assert request.body == expected_data


def test_remove_all_dict_post_data_parameters():
    body = {"id": "secret", "foo": "bar"}
    request = Request("POST", "http://google.com", body, {})
    request.headers["Content-Type"] = "application/x-www-form-urlencoded"
    replace_post_data_parameters(request, [("id", None), ("foo", None)])
    assert request.body == {}


def test_decode_response_uncompressed():
    recorded_response = {
        "status": {"message": "OK", "code": 200},
        "headers": {
            "content-length": ["10806"],
            "date": ["Fri, 24 Oct 2014 18:35:37 GMT"],
            "content-type": ["text/html; charset=utf-8"],
        },
        "body": {"string": b""},
    }
    assert decode_response(recorded_response) == recorded_response


def test_decode_response_deflate():
    body = b"deflate message"
    deflate_response = {
        "body": {"string": zlib.compress(body)},
        "headers": {
            "access-control-allow-credentials": ["true"],
            "access-control-allow-origin": ["*"],
            "connection": ["keep-alive"],
            "content-encoding": ["deflate"],
            "content-length": ["177"],
            "content-type": ["application/json"],
            "date": ["Wed, 02 Dec 2015 19:44:32 GMT"],
            "server": ["nginx"],
        },
        "status": {"code": 200, "message": "OK"},
    }
    decoded_response = decode_response(deflate_response)
    assert decoded_response["body"]["string"] == body
    assert decoded_response["headers"]["content-length"] == [str(len(body))]


def test_decode_response_deflate_already_decompressed():
    body = b"deflate message"
    gzip_response = {
        "body": {"string": body},
        "headers": {
            "content-encoding": ["deflate"],
        },
    }
    decoded_response = decode_response(gzip_response)
    assert decoded_response["body"]["string"] == body


def test_decode_response_gzip():
    body = b"gzip message"

    buf = BytesIO()
    f = gzip.GzipFile("a", fileobj=buf, mode="wb")
    f.write(body)
    f.close()

    compressed_body = buf.getvalue()
    buf.close()
    gzip_response = {
        "body": {"string": compressed_body},
        "headers": {
            "access-control-allow-credentials": ["true"],
            "access-control-allow-origin": ["*"],
            "connection": ["keep-alive"],
            "content-encoding": ["gzip"],
            "content-length": ["177"],
            "content-type": ["application/json"],
            "date": ["Wed, 02 Dec 2015 19:44:32 GMT"],
            "server": ["nginx"],
        },
        "status": {"code": 200, "message": "OK"},
    }
    decoded_response = decode_response(gzip_response)
    assert decoded_response["body"]["string"] == body
    assert decoded_response["headers"]["content-length"] == [str(len(body))]


def test_decode_response_gzip_already_decompressed():
    body = b"gzip message"
    gzip_response = {
        "body": {"string": body},
        "headers": {
            "content-encoding": ["gzip"],
        },
    }
    decoded_response = decode_response(gzip_response)
    assert decoded_response["body"]["string"] == body
