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

import curses

import six
import pytest

from rtv import __version__
from rtv.subreddit_page import SubredditPage
from rtv.packages.praw.errors import NotFound, HTTPException
from requests.exceptions import ReadTimeout

try:
    from unittest import mock
except ImportError:
    import mock


def test_subreddit_page_construct(reddit, terminal, config, oauth):
    window = terminal.stdscr.subwin

    with terminal.loader():
        page = SubredditPage(reddit, terminal, config, oauth, '/r/python')
    assert terminal.loader.exception is None
    page.draw()

    # Title
    title = '/r/python'.encode('utf-8')
    window.addstr.assert_any_call(0, 0, title)

    # Banner
    menu = '[1]hot     [2]top     [3]rising     [4]new     [5]controversial     [6]gilded'.encode('utf-8')
    window.addstr.assert_any_call(0, 0, menu)

    # Submission
    text = page.content.get(0)['split_title'][0].encode('utf-8')
    window.subwin.addstr.assert_any_call(0, 1, text, 2097152)

    # Cursor should have been drawn
    window.subwin.addch.assert_any_call(0, 0, ' ', curses.A_REVERSE)

    # Reload with a smaller terminal window
    terminal.stdscr.ncols = 20
    terminal.stdscr.nlines = 10
    with terminal.loader():
        page = SubredditPage(reddit, terminal, config, oauth, '/r/python')
    assert terminal.loader.exception is None
    page.draw()


def test_subreddit_refresh(subreddit_page, terminal):

    # Refresh the page with default values
    subreddit_page.controller.trigger('r')
    assert subreddit_page.content.order is None
    assert subreddit_page.content.name == '/r/python'
    assert terminal.loader.exception is None

    # Refresh with the order in the name
    subreddit_page.refresh_content(order='ignore', name='/r/front/hot')
    assert subreddit_page.content.order == 'hot'
    assert subreddit_page.content.name == '/r/front'
    assert terminal.loader.exception is None


def test_subreddit_reload_page(subreddit_page, terminal, reddit):

    cache = reddit.handler.cache
    assert len(cache) == 1

    # A plain refresh_content() will use whatever is in the praw cache
    # instead of making a new request to reddit
    list(cache.values())[0].status_code = 503
    subreddit_page.refresh_content()
    assert isinstance(terminal.loader.exception, HTTPException)

    cache = reddit.handler.cache
    assert len(cache) == 1

    # But if we manually trigger a page refresh, it should clear the cache
    # and reload the page instead of returning the cached 503 response
    list(cache.values())[0].status_code = 503
    subreddit_page.controller.trigger('r')
    assert terminal.loader.exception is None


def test_subreddit_title(subreddit_page, terminal, capsys):
    subreddit_page.content.name = 'hello ❤'

    with mock.patch.dict('os.environ', {'DISPLAY': ':1'}):
        terminal.config['ascii'] = True
        subreddit_page.draw()
        out, _ = capsys.readouterr()
        assert isinstance(out, six.text_type)
        assert out == '\x1b]2;hello ? - rtv {}\x07'.format(__version__)

        terminal.config['ascii'] = False
        subreddit_page.draw()
        out, _ = capsys.readouterr()
        assert isinstance(out, six.text_type)
        assert out == '\x1b]2;hello ❤ - rtv {}\x07'.format(__version__)

    with mock.patch.dict('os.environ', {'DISPLAY': ''}):
        subreddit_page.draw()
        out, _ = capsys.readouterr()
        assert not out

    with mock.patch.dict('os.environ', {'INSIDE_EMACS': '25.3.1,term:0.96'}):
        subreddit_page.draw()
        out, _ = capsys.readouterr()
        assert not out


def test_subreddit_search(subreddit_page, terminal):
    window = terminal.stdscr.subwin

    # Search the current subreddit
    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = 'search term'
        subreddit_page.controller.trigger('f')
        assert subreddit_page.content.name == '/r/python'
        assert terminal.prompt_input.called
        assert not terminal.loader.exception

        # The page title should display the query
        subreddit_page.draw()
        title = 'Searching /r/python: search term'.encode('utf-8')
        window.addstr.assert_any_call(0, 0, title)

    # Ordering the results should preserve the query
    window.addstr.reset_mock()
    subreddit_page.refresh_content(order='hot')
    subreddit_page.refresh_content(order='top-all')
    subreddit_page.refresh_content(order='new')
    assert subreddit_page.content.name == '/r/python'
    assert subreddit_page.content.query == 'search term'
    assert not terminal.loader.exception

    # Searching with an empty query shouldn't crash
    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = None
        subreddit_page.controller.trigger('f')
        assert not terminal.loader.exception

    # Changing to a new subreddit should clear the query
    window.addstr.reset_mock()
    subreddit_page.refresh_content(name='/r/learnpython')
    assert subreddit_page.content.query is None


def test_subreddit_prompt(subreddit_page, terminal):

    # Prompt for a different subreddit
    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = 'front/top'
        subreddit_page.controller.trigger('/')
        assert subreddit_page.content.name == '/r/front'
        assert subreddit_page.content.order == 'top'
        assert not terminal.loader.exception


def test_subreddit_prompt_submission(subreddit_page, terminal):

    prompts = [
        'comments/571dw3',
        '///comments/571dw3',
        '/comments/571dw3',
        '/r/pics/comments/571dw3/',
        'https://www.reddit.com/r/pics/comments/571dw3/at_disneyland']
    url = 'https://www.reddit.com/comments/571dw3'

    for text in prompts:
        with mock.patch.object(subreddit_page, 'open_submission'), \
                mock.patch.object(terminal, 'prompt_input'):

            terminal.prompt_input.return_value = text
            subreddit_page.controller.trigger('/')
            subreddit_page.open_submission.assert_called_with(url)
            assert not terminal.loader.exception


def test_subreddit_prompt_submission_invalid(subreddit_page, terminal):

    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = 'comments/571dw3fakeid'
        subreddit_page.controller.trigger('/')
        assert isinstance(terminal.loader.exception, NotFound)


def test_subreddit_order(subreddit_page):

    # /r/python doesn't always have rising submissions, so use a larger sub
    subreddit_page.refresh_content(name='all')

    subreddit_page.content.query = ''
    subreddit_page.controller.trigger('1')
    assert subreddit_page.content.order == 'hot'
    subreddit_page.controller.trigger('3')
    assert subreddit_page.content.order == 'rising'
    subreddit_page.controller.trigger('4')
    assert subreddit_page.content.order == 'new'
    subreddit_page.controller.trigger('6')
    assert subreddit_page.content.order == 'gilded'

    subreddit_page.content.query = 'search text'
    subreddit_page.controller.trigger('1')
    assert subreddit_page.content.order == 'relevance'
    subreddit_page.controller.trigger('4')
    assert subreddit_page.content.order == 'new'

    # Shouldn't be able to sort queries by gilded
    subreddit_page.controller.trigger('6')
    assert curses.flash.called
    assert subreddit_page.content.order == 'new'


def test_subreddit_order_top(subreddit_page, terminal):

    # Sort by top
    with mock.patch.object(terminal, 'show_notification'):
        # Invalid selection
        terminal.show_notification.return_value = ord('x')
        subreddit_page.controller.trigger('2')
        terminal.show_notification.assert_called_with('Invalid option')
        assert subreddit_page.content.order is None

        # Valid selection - sort by week
        terminal.show_notification.reset_mock()
        terminal.show_notification.return_value = ord('3')
        subreddit_page.controller.trigger('2')
        assert subreddit_page.content.order == 'top-week'


def test_subreddit_order_controversial(subreddit_page, terminal):

    # Sort by controversial
    with mock.patch.object(terminal, 'show_notification'):
        # Invalid selection
        terminal.show_notification.return_value = ord('x')
        subreddit_page.controller.trigger('5')
        terminal.show_notification.assert_called_with('Invalid option')
        assert subreddit_page.content.order is None

        # Valid selection - sort by default
        terminal.show_notification.reset_mock()
        terminal.show_notification.return_value = ord('\n')
        subreddit_page.controller.trigger('5')
        assert subreddit_page.content.order == 'controversial'


def test_subreddit_order_search(subreddit_page, terminal):

    # Search the current subreddit
    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = 'search term'
        subreddit_page.controller.trigger('f')
        assert subreddit_page.content.name == '/r/python'
        assert terminal.prompt_input.called
        assert not terminal.loader.exception

    # Sort by relevance
    subreddit_page.controller.trigger('1')
    assert subreddit_page.content.order == 'relevance'

    # Sort by top
    with mock.patch.object(terminal, 'show_notification'):
        terminal.show_notification.reset_mock()
        terminal.show_notification.return_value = ord('6')
        subreddit_page.controller.trigger('2')
        assert subreddit_page.content.order == 'top-all'

    # Sort by comments
    with mock.patch.object(terminal, 'show_notification'):
        terminal.show_notification.reset_mock()
        terminal.show_notification.return_value = ord('6')
        subreddit_page.controller.trigger('3')
        assert subreddit_page.content.order == 'comments-all'

    # Sort by new
    subreddit_page.controller.trigger('4')
    assert subreddit_page.content.order == 'new'


def test_subreddit_open(subreddit_page, terminal, config):

    # Open the selected submission
    data = subreddit_page.content.get(subreddit_page.nav.absolute_index)
    with mock.patch('rtv.submission_page.SubmissionPage.loop') as loop, \
            mock.patch.object(config.history, 'add'):
        data['url_type'] = 'selfpost'
        subreddit_page.controller.trigger('l')
        assert not terminal.loader.exception
        assert loop.called
        config.history.add.assert_called_with(data['url_full'])

    # Open the selected link externally
    data = subreddit_page.content.get(subreddit_page.nav.absolute_index)
    with mock.patch.object(terminal, 'open_link'), \
            mock.patch.object(config.history, 'add'):
        data['url_type'] = 'external'
        subreddit_page.controller.trigger('o')
        assert terminal.open_link.called
        config.history.add.assert_called_with(data['url_full'])

    # Open the selected link within rtv
    data = subreddit_page.content.get(subreddit_page.nav.absolute_index)
    with mock.patch.object(subreddit_page, 'open_submission'), \
            mock.patch.object(config.history, 'add'):
        data['url_type'] = 'selfpost'
        subreddit_page.controller.trigger('o')
        assert subreddit_page.open_submission.called


def test_subreddit_open_xpost(subreddit_page, config):

    data = subreddit_page.content.get(subreddit_page.nav.absolute_index)

    # Open an x-post subreddit, see /r/TinySubredditoftheDay for an example
    with mock.patch.object(subreddit_page, 'refresh_content'):
        data['url_type'] = 'x-post subreddit'
        data['xpost_subreddit'] = 'goodbye'
        subreddit_page.controller.trigger('o')
        subreddit_page.refresh_content.assert_called_with(
            name='goodbye', order='ignore')

    # Open an x-post submission, see /r/bestof for an example
    with mock.patch.object(subreddit_page, 'open_submission'):
        data['url_type'] = 'x-post submission'
        data['url_full'] = 'www.test.com'
        subreddit_page.controller.trigger('o')
        subreddit_page.open_submission.assert_called_with(url='www.test.com')


def test_subreddit_unauthenticated(subreddit_page, terminal):

    # Unauthenticated commands
    methods = [
        'a',  # Upvote
        'z',  # Downvote
        'c',  # Post
        'e',  # Edit
        'd',  # Delete
        's',  # Subscriptions
    ]
    for ch in methods:
        subreddit_page.controller.trigger(ch)
        text = 'Not logged in'.encode('utf-8')
        terminal.stdscr.subwin.addstr.assert_called_with(1, 1, text)


def test_subreddit_post(subreddit_page, terminal, reddit, refresh_token):

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    # Post a submission to an invalid subreddit
    subreddit_page.refresh_content(name='front')
    subreddit_page.controller.trigger('c')
    text = "Can't post to /r/front".encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_called_with(1, 1, text)

    # Post a submission with a title but with no body
    subreddit_page.refresh_content(name='python')
    with mock.patch.object(terminal, 'open_editor'):
        terminal.open_editor.return_value.__enter__.return_value = 'title'
        subreddit_page.controller.trigger('c')
        text = 'Missing body'.encode('utf-8')
        terminal.stdscr.subwin.addstr.assert_called_with(1, 1, text)

    # Post a fake submission
    url = 'https://www.reddit.com/r/Python/comments/2xmo63/'
    submission = reddit.get_submission(url)
    with mock.patch.object(terminal, 'open_editor'),  \
            mock.patch.object(reddit, 'submit'),      \
            mock.patch('rtv.page.Page.loop') as loop, \
            mock.patch('time.sleep'):
        terminal.open_editor.return_value.__enter__.return_value = 'test\ncont'
        reddit.submit.return_value = submission
        subreddit_page.controller.trigger('c')
        assert reddit.submit.called
        assert loop.called


def test_subreddit_open_subscriptions(subreddit_page, refresh_token):

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    # Open subscriptions
    with mock.patch('rtv.page.Page.loop') as loop:
        subreddit_page.controller.trigger('s')
        assert loop.called


def test_subreddit_get_inbox_timeout(subreddit_page, refresh_token, terminal, vcr):
    if vcr.record_mode == 'none':
        pytest.skip('Unable to test ReadTimeout exceptions using a cassette')

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    subreddit_page.reddit.config.timeout = 0.00000001
    subreddit_page.controller.trigger('i')
    text = 'HTTP request timed out'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_called_with(1, 1, text)
    assert isinstance(terminal.loader.exception, ReadTimeout)


def test_subreddit_open_multireddits(subreddit_page, refresh_token):

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    # Open multireddits
    with mock.patch('rtv.page.Page.loop') as loop:
        subreddit_page.controller.trigger('S')
        assert loop.called


def test_subreddit_saved(subreddit_page, refresh_token):

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    subreddit_page.refresh_content(name='/u/saved')
    subreddit_page.draw()


def test_subreddit_user_overview(subreddit_page, refresh_token):

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    # Pick a user that has a lot of recent comments, so we can make sure that
    # SavedComment objects have all of the properties necessary to be drawn
    # on the submission page.
    subreddit_page.refresh_content(name='/u/spez')
    subreddit_page.draw()


def test_subreddit_draw_header(subreddit_page, refresh_token, terminal):

    # /r/front alias should be renamed in the header
    subreddit_page.refresh_content(name='/r/front')
    subreddit_page.draw()
    text = 'Front Page'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(0, 0, text)

    subreddit_page.refresh_content(name='/r/front/new')
    subreddit_page.draw()
    text = 'Front Page'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(0, 0, text)

    # Log in to check the user submissions page
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    # /u/me alias should be renamed in the header
    subreddit_page.refresh_content(name='/u/me')
    subreddit_page.draw()
    text = 'My Submissions'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(0, 0, text)

    subreddit_page.refresh_content(name='/u/me/new')
    subreddit_page.draw()
    text = 'My Submissions'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(0, 0, text)

    # /u/saved alias should be renamed in the header
    subreddit_page.refresh_content(name='/u/saved')
    subreddit_page.draw()
    text = 'My Saved Submissions'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(0, 0, text)

    subreddit_page.refresh_content(name='/u/saved/new')
    subreddit_page.draw()
    text = 'My Saved Submissions'.encode('utf-8')
    terminal.stdscr.subwin.addstr.assert_any_call(0, 0, text)


def test_subreddit_frontpage_toggle(subreddit_page, terminal):

    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = 'aww'
        subreddit_page.controller.trigger('/')
        assert subreddit_page.content.name == '/r/aww'
        subreddit_page.controller.trigger('p')
        assert subreddit_page.content.name == '/r/front'


def test_subreddit_hide_submission(subreddit_page, refresh_token):

    # Log in
    subreddit_page.config.refresh_token = refresh_token
    subreddit_page.oauth.authorize()

    # The api won't return hidden posts in the submission listing, so the
    # first post should always have hidden set to false
    data = subreddit_page.get_selected_item()
    assert data['hidden'] is False

    # Hide the first submission by pressing the space key
    subreddit_page.controller.trigger(0x20)
    assert subreddit_page.term.loader.exception is None
    data = subreddit_page.get_selected_item()
    assert data['hidden'] is True

    # Make sure that the status was actually updated on the server side
    data['object'].refresh()
    assert data['object'].hidden is True

    # Now undo the hide by pressing space again
    subreddit_page.controller.trigger(0x20)
    assert subreddit_page.term.loader.exception is None
    data = subreddit_page.get_selected_item()
    assert data['hidden'] is False

    # Make sure that the status was actually updated on the server side
    data['object'].refresh()
    assert data['object'].hidden is False