# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import requests

from tuir.oauth import OAuthHelper, OAuthHandler
from tuir.exceptions import InvalidRefreshToken
from tuir.packages.praw.errors import OAuthException


try:
    from unittest import mock
except ImportError:
    import mock


def test_oauth_handler_not_found(oauth_server):

    url = oauth_server.url + 'favicon.ico'
    resp = requests.get(url)
    assert resp.status_code == 404


def test_oauth_handler_no_callback(oauth_server):

    resp = requests.get(oauth_server.url)
    assert resp.status_code == 200
    assert 'Wait...' in resp.text
    assert OAuthHandler.params['error'] is None


def test_oauth_handler_access_denied(oauth_server):

    url = oauth_server.url + '?error=access_denied'
    resp = requests.get(url)
    assert resp.status_code == 200
    assert OAuthHandler.params['error'] == 'access_denied'
    assert 'denied access' in resp.text


def test_oauth_handler_error(oauth_server):

    url = oauth_server.url + '?error=fake'
    resp = requests.get(url)
    assert resp.status_code == 200
    assert OAuthHandler.params['error'] == 'fake'
    assert 'fake' in resp.text


def test_oauth_handler_success(oauth_server):

    url = oauth_server.url + '?state=fake_state&code=fake_code'
    resp = requests.get(url)
    assert resp.status_code == 200
    assert OAuthHandler.params['error'] is None
    assert OAuthHandler.params['state'] == 'fake_state'
    assert OAuthHandler.params['code'] == 'fake_code'
    assert 'Access Granted' in resp.text


def test_oauth_terminal_non_mobile_authorize(reddit, terminal, config):

    # Should direct to the desktop version if using a graphical browser
    reddit.config.API_PATHS['authorize'] = 'api/v1/authorize/'
    terminal._display = True
    oauth = OAuthHelper(reddit, terminal, config)
    assert '.compact' not in oauth.reddit.config.API_PATHS['authorize']


def test_oauth_terminal_mobile_authorize(reddit, terminal, config):

    # Should direct to the mobile version if using a terminal browser
    reddit.config.API_PATHS['authorize'] = 'api/v1/authorize/'
    terminal._display = False
    oauth = OAuthHelper(reddit, terminal, config)
    assert '.compact' in oauth.reddit.config.API_PATHS['authorize']


def test_oauth_authorize_invalid_token(oauth):

    oauth.config.refresh_token = 'invalid_token'
    oauth.authorize()

    assert isinstance(oauth.term.loader.exception, InvalidRefreshToken)
    assert oauth.server is None
    assert oauth.config.refresh_token is None


def test_oauth_authorize_with_refresh_token(oauth, refresh_token):

    oauth.config.refresh_token = refresh_token
    oauth.authorize(autologin=True)
    assert oauth.server is None

    # We should be able to handle an oauth failure
    with mock.patch.object(oauth.reddit, 'refresh_access_information'):
        exception = OAuthException('', '')
        oauth.reddit.refresh_access_information.side_effect = exception
        oauth.authorize()

    assert isinstance(oauth.term.loader.exception, InvalidRefreshToken)
    assert oauth.server is None
    assert oauth.config.refresh_token is None


def test_oauth_authorize_without_autologin(oauth, terminal, refresh_token):

    # The welcome message should be displayed when autologin is set to
    # false, even if we're using an existing refresh token and not performing
    # the whole login procedure.
    oauth.config.refresh_token = refresh_token
    oauth.authorize(autologin=False)

    text = 'Welcome civilization_phaze_3!'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(1, 1, text)


def test_oauth_clear_data(oauth):
    oauth.config.refresh_token = 'secrettoken'
    oauth.reddit.refresh_token = 'secrettoken'
    oauth.clear_oauth_data()
    assert oauth.config.refresh_token is None
    assert oauth.reddit.refresh_token is None


def test_oauth_authorize(oauth, reddit, stdscr, refresh_token):

    # Because we use `from .helpers import open_browser` we have to patch the
    # function in the destination oauth module and not the helpers module
    with mock.patch('uuid.UUID.hex', new_callable=mock.PropertyMock) as uuid, \
            mock.patch('tuir.terminal.Terminal.open_browser') as open_browser, \
            mock.patch('tuir.oauth.OAuthHTTPServer') as http_server,           \
            mock.patch.object(oauth.reddit, 'user'),                          \
            mock.patch('time.sleep'):

        # Valid authorization
        oauth.term._display = False
        params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None}
        uuid.return_value = params['state']

        def serve_forever():
            oauth.params.update(**params)
        http_server.return_value.serve_forever.side_effect = serve_forever

        oauth.authorize()
        assert open_browser.called
        oauth.reddit.get_access_information.assert_called_with(
            reddit, params['code'])
        assert oauth.config.refresh_token is not None
        assert oauth.config.save_refresh_token.called

        stdscr.reset_mock()
        oauth.reddit.get_access_information.reset_mock()
        oauth.config.save_refresh_token.reset_mock()
        oauth.server = None

        # The next authorization should skip the oauth process
        oauth.config.refresh_token = refresh_token
        oauth.authorize()
        assert oauth.reddit.user is not None
        assert oauth.server is None
        stdscr.reset_mock()

        # Invalid state returned
        params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None}
        oauth.config.refresh_token = None
        uuid.return_value = 'invalidcode'
        oauth.authorize()
        error_message = 'UUID mismatch'.encode('utf-8')
        stdscr.subwin.addstr.assert_any_call(1, 1, error_message)

        # Valid authorization, terminal browser
        oauth.term._display = True
        params = {'state': 'uniqueid', 'code': 'secretcode', 'error': None}
        uuid.return_value = params['state']

        oauth.authorize()
        assert open_browser.called
        oauth.reddit.get_access_information.assert_called_with(
            reddit, params['code'])
        assert oauth.config.refresh_token is not None
        assert oauth.config.save_refresh_token.called

        stdscr.reset_mock()
        oauth.reddit.get_access_information.reset_mock()
        oauth.config.refresh_token = None
        oauth.config.save_refresh_token.reset_mock()
        oauth.server = None

        # Exceptions when logging in are handled correctly
        with mock.patch.object(oauth.reddit, 'get_access_information'):
            exception = OAuthException('', '')
            oauth.reddit.get_access_information.side_effect = exception
            oauth.authorize()
        assert isinstance(oauth.term.loader.exception, OAuthException)
        assert not oauth.config.save_refresh_token.called
