# -*- coding: utf-8 -*-
# (c) 2018 Matt Martz <matt@sivel.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

import datetime
import os

from ansible.module_utils.urls import (Request, open_url, urllib_request, HAS_SSLCONTEXT, cookiejar, ConnectionError, RequestWithMethod,
                                       UnixHTTPHandler, UnixHTTPSConnection, httplib)
from ansible.module_utils.urls import SSLValidationHandler, HTTPSClientAuthHandler, RedirectHandlerFactory

import pytest
from mock import call


if HAS_SSLCONTEXT:
    import ssl


@pytest.fixture
def urlopen_mock(mocker):
    return mocker.patch('ansible.module_utils.urls.urllib_request.urlopen')


@pytest.fixture
def install_opener_mock(mocker):
    return mocker.patch('ansible.module_utils.urls.urllib_request.install_opener')


def test_Request_fallback(urlopen_mock, install_opener_mock, mocker):
    cookies = cookiejar.CookieJar()
    request = Request(
        headers={'foo': 'bar'},
        use_proxy=False,
        force=True,
        timeout=100,
        validate_certs=False,
        url_username='user',
        url_password='passwd',
        http_agent='ansible-tests',
        force_basic_auth=True,
        follow_redirects='all',
        client_cert='/tmp/client.pem',
        client_key='/tmp/client.key',
        cookies=cookies,
        unix_socket='/foo/bar/baz.sock',
    )
    fallback_mock = mocker.spy(request, '_fallback')

    r = request.open('GET', 'https://ansible.com')

    calls = [
        call(None, False),  # use_proxy
        call(None, True),  # force
        call(None, 100),  # timeout
        call(None, False),  # validate_certs
        call(None, 'user'),  # url_username
        call(None, 'passwd'),  # url_password
        call(None, 'ansible-tests'),  # http_agent
        call(None, True),  # force_basic_auth
        call(None, 'all'),  # follow_redirects
        call(None, '/tmp/client.pem'),  # client_cert
        call(None, '/tmp/client.key'),  # client_key
        call(None, cookies),  # cookies
        call(None, '/foo/bar/baz.sock'),  # unix_socket
    ]
    fallback_mock.assert_has_calls(calls)

    assert fallback_mock.call_count == 13  # All but headers use fallback

    args = urlopen_mock.call_args[0]
    assert args[1] is None  # data, this is handled in the Request not urlopen
    assert args[2] == 100  # timeout

    req = args[0]
    assert req.headers == {
        'Authorization': b'Basic dXNlcjpwYXNzd2Q=',
        'Cache-control': 'no-cache',
        'Foo': 'bar',
        'User-agent': 'ansible-tests'
    }
    assert req.data is None
    assert req.get_method() == 'GET'


def test_Request_open(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'https://ansible.com/')
    args = urlopen_mock.call_args[0]
    assert args[1] is None  # data, this is handled in the Request not urlopen
    assert args[2] == 10  # timeout

    req = args[0]
    assert req.headers == {}
    assert req.data is None
    assert req.get_method() == 'GET'

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    expected_handlers = (
        SSLValidationHandler,
        RedirectHandlerFactory(),  # factory, get handler
    )

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, SSLValidationHandler) or handler.__class__.__name__ == 'RedirectHandler':
            found_handlers.append(handler)

    assert len(found_handlers) == 2


def test_Request_open_http(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://ansible.com/')
    args = urlopen_mock.call_args[0]

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, SSLValidationHandler):
            found_handlers.append(handler)

    assert len(found_handlers) == 0


def test_Request_open_unix_socket(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://ansible.com/', unix_socket='/foo/bar/baz.sock')
    args = urlopen_mock.call_args[0]

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, UnixHTTPHandler):
            found_handlers.append(handler)

    assert len(found_handlers) == 1


def test_Request_open_https_unix_socket(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'https://ansible.com/', unix_socket='/foo/bar/baz.sock')
    args = urlopen_mock.call_args[0]

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, HTTPSClientAuthHandler):
            found_handlers.append(handler)

    assert len(found_handlers) == 1

    inst = found_handlers[0]._build_https_connection('foo')
    assert isinstance(inst, UnixHTTPSConnection)


def test_Request_open_ftp(urlopen_mock, install_opener_mock, mocker):
    mocker.patch('ansible.module_utils.urls.ParseResultDottedDict.as_list', side_effect=AssertionError)

    # Using ftp scheme should prevent the AssertionError side effect to fire
    r = Request().open('GET', 'ftp://foo@ansible.com/')


def test_Request_open_headers(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://ansible.com/', headers={'Foo': 'bar'})
    args = urlopen_mock.call_args[0]
    req = args[0]
    assert req.headers == {'Foo': 'bar'}


def test_Request_open_username(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://ansible.com/', url_username='user')

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    expected_handlers = (
        urllib_request.HTTPBasicAuthHandler,
        urllib_request.HTTPDigestAuthHandler,
    )

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, expected_handlers):
            found_handlers.append(handler)
    assert len(found_handlers) == 2
    assert found_handlers[0].passwd.passwd[None] == {(('ansible.com', '/'),): ('user', None)}


def test_Request_open_username_in_url(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://user2@ansible.com/')

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    expected_handlers = (
        urllib_request.HTTPBasicAuthHandler,
        urllib_request.HTTPDigestAuthHandler,
    )

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, expected_handlers):
            found_handlers.append(handler)
    assert found_handlers[0].passwd.passwd[None] == {(('ansible.com', '/'),): ('user2', '')}


def test_Request_open_username_force_basic(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://ansible.com/', url_username='user', url_password='passwd', force_basic_auth=True)

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    expected_handlers = (
        urllib_request.HTTPBasicAuthHandler,
        urllib_request.HTTPDigestAuthHandler,
    )

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, expected_handlers):
            found_handlers.append(handler)

    assert len(found_handlers) == 0

    args = urlopen_mock.call_args[0]
    req = args[0]
    assert req.headers.get('Authorization') == b'Basic dXNlcjpwYXNzd2Q='


def test_Request_open_auth_in_netloc(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'http://user:passwd@ansible.com/')
    args = urlopen_mock.call_args[0]
    req = args[0]
    assert req.get_full_url() == 'http://ansible.com/'

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    expected_handlers = (
        urllib_request.HTTPBasicAuthHandler,
        urllib_request.HTTPDigestAuthHandler,
    )

    found_handlers = []
    for handler in handlers:
        if isinstance(handler, expected_handlers):
            found_handlers.append(handler)

    assert len(found_handlers) == 2


def test_Request_open_netrc(urlopen_mock, install_opener_mock, monkeypatch):
    here = os.path.dirname(__file__)

    monkeypatch.setenv('NETRC', os.path.join(here, 'fixtures/netrc'))
    r = Request().open('GET', 'http://ansible.com/')
    args = urlopen_mock.call_args[0]
    req = args[0]
    assert req.headers.get('Authorization') == b'Basic dXNlcjpwYXNzd2Q='

    r = Request().open('GET', 'http://foo.ansible.com/')
    args = urlopen_mock.call_args[0]
    req = args[0]
    assert 'Authorization' not in req.headers

    monkeypatch.setenv('NETRC', os.path.join(here, 'fixtures/netrc.nonexistant'))
    r = Request().open('GET', 'http://ansible.com/')
    args = urlopen_mock.call_args[0]
    req = args[0]
    assert 'Authorization' not in req.headers


def test_Request_open_no_proxy(urlopen_mock, install_opener_mock, mocker):
    build_opener_mock = mocker.patch('ansible.module_utils.urls.urllib_request.build_opener')

    r = Request().open('GET', 'http://ansible.com/', use_proxy=False)

    handlers = build_opener_mock.call_args[0]
    found_handlers = []
    for handler in handlers:
        if isinstance(handler, urllib_request.ProxyHandler):
            found_handlers.append(handler)

    assert len(found_handlers) == 1


@pytest.mark.skipif(not HAS_SSLCONTEXT, reason="requires SSLContext")
def test_Request_open_no_validate_certs(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'https://ansible.com/', validate_certs=False)

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    ssl_handler = None
    for handler in handlers:
        if isinstance(handler, HTTPSClientAuthHandler):
            ssl_handler = handler
            break

    assert ssl_handler is not None

    inst = ssl_handler._build_https_connection('foo')
    assert isinstance(inst, httplib.HTTPSConnection)

    context = ssl_handler._context
    assert context.protocol == ssl.PROTOCOL_SSLv23
    if ssl.OP_NO_SSLv2:
        assert context.options & ssl.OP_NO_SSLv2
    assert context.options & ssl.OP_NO_SSLv3
    assert context.verify_mode == ssl.CERT_NONE
    assert context.check_hostname is False


def test_Request_open_client_cert(urlopen_mock, install_opener_mock):
    here = os.path.dirname(__file__)

    client_cert = os.path.join(here, 'fixtures/client.pem')
    client_key = os.path.join(here, 'fixtures/client.key')

    r = Request().open('GET', 'https://ansible.com/', client_cert=client_cert, client_key=client_key)

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    ssl_handler = None
    for handler in handlers:
        if isinstance(handler, HTTPSClientAuthHandler):
            ssl_handler = handler
            break

    assert ssl_handler is not None

    assert ssl_handler.client_cert == client_cert
    assert ssl_handler.client_key == client_key

    https_connection = ssl_handler._build_https_connection('ansible.com')

    assert https_connection.key_file == client_key
    assert https_connection.cert_file == client_cert


def test_Request_open_cookies(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'https://ansible.com/', cookies=cookiejar.CookieJar())

    opener = install_opener_mock.call_args[0][0]
    handlers = opener.handlers

    cookies_handler = None
    for handler in handlers:
        if isinstance(handler, urllib_request.HTTPCookieProcessor):
            cookies_handler = handler
            break

    assert cookies_handler is not None


def test_Request_open_invalid_method(urlopen_mock, install_opener_mock):
    r = Request().open('UNKNOWN', 'https://ansible.com/')

    args = urlopen_mock.call_args[0]
    req = args[0]

    assert req.data is None
    assert req.get_method() == 'UNKNOWN'
    # assert r.status == 504


def test_Request_open_custom_method(urlopen_mock, install_opener_mock):
    r = Request().open('DELETE', 'https://ansible.com/')

    args = urlopen_mock.call_args[0]
    req = args[0]

    assert isinstance(req, RequestWithMethod)


def test_Request_open_user_agent(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'https://ansible.com/', http_agent='ansible-tests')

    args = urlopen_mock.call_args[0]
    req = args[0]

    assert req.headers.get('User-agent') == 'ansible-tests'


def test_Request_open_force(urlopen_mock, install_opener_mock):
    r = Request().open('GET', 'https://ansible.com/', force=True, last_mod_time=datetime.datetime.now())

    args = urlopen_mock.call_args[0]
    req = args[0]

    assert req.headers.get('Cache-control') == 'no-cache'
    assert 'If-modified-since' not in req.headers


def test_Request_open_last_mod(urlopen_mock, install_opener_mock):
    now = datetime.datetime.now()
    r = Request().open('GET', 'https://ansible.com/', last_mod_time=now)

    args = urlopen_mock.call_args[0]
    req = args[0]

    assert req.headers.get('If-modified-since') == now.strftime('%a, %d %b %Y %H:%M:%S -0000')


def test_Request_open_headers_not_dict(urlopen_mock, install_opener_mock):
    with pytest.raises(ValueError):
        Request().open('GET', 'https://ansible.com/', headers=['bob'])


def test_Request_init_headers_not_dict(urlopen_mock, install_opener_mock):
    with pytest.raises(ValueError):
        Request(headers=['bob'])


@pytest.mark.parametrize('method,kwargs', [
    ('get', {}),
    ('options', {}),
    ('head', {}),
    ('post', {'data': None}),
    ('put', {'data': None}),
    ('patch', {'data': None}),
    ('delete', {}),
])
def test_methods(method, kwargs, mocker):
    expected = method.upper()
    open_mock = mocker.patch('ansible.module_utils.urls.Request.open')
    request = Request()
    getattr(request, method)('https://ansible.com')
    open_mock.assert_called_once_with(expected, 'https://ansible.com', **kwargs)


def test_open_url(urlopen_mock, install_opener_mock, mocker):
    req_mock = mocker.patch('ansible.module_utils.urls.Request.open')
    open_url('https://ansible.com/')
    req_mock.assert_called_once_with('GET', 'https://ansible.com/', data=None, headers=None, use_proxy=True,
                                     force=False, last_mod_time=None, timeout=10, validate_certs=True,
                                     url_username=None, url_password=None, http_agent=None,
                                     force_basic_auth=False, follow_redirects='urllib2',
                                     client_cert=None, client_key=None, cookies=None, use_gssapi=False,
                                     unix_socket=None)
