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

import curses
from collections import OrderedDict

import pytest

from tuir.submission_page import SubmissionPage
from tuir.docs import FOOTER_SUBMISSION

try:
    from unittest import mock
except ImportError:
    import mock


PROMPTS = OrderedDict([
    ('prompt_1', 'comments/571dw3'),
    ('prompt_2', '///comments/571dw3'),
    ('prompt_3', '/comments/571dw3'),
    ('prompt_4', '/r/pics/comments/571dw3/'),
    ('prompt_5', 'https://www.reddit.com/r/pics/comments/571dw3/at_disneyland'),
])


def test_submission_page_construct(reddit, terminal, config, oauth):
    window = terminal.stdscr.subwin
    url = ('https://www.reddit.com/r/Python/comments/2xmo63/'
           'a_python_terminal_viewer_for_browsing_reddit')

    with terminal.loader():
        page = SubmissionPage(reddit, terminal, config, oauth, url=url)
    assert terminal.loader.exception is None

    # Toggle the second comment so we can check the draw more comments method
    page.content.toggle(1)

    # Set some special flags to make sure that we can draw them
    submission_data = page.content.get(-1)
    submission_data['gold'] = 1
    submission_data['stickied'] = True
    submission_data['saved'] = True
    submission_data['flair'] = 'flair'

    # Set some special flags to make sure that we can draw them
    comment_data = page.content.get(0)
    comment_data['gold'] = 3
    comment_data['stickied'] = True
    comment_data['saved'] = True
    comment_data['flair'] = 'flair'

    page.draw()

    #  Title
    title = url[:terminal.stdscr.ncols-1].encode('utf-8')
    window.addstr.assert_any_call(0, 0, title)

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

    # Footer - The text is longer than the default terminal width
    text = FOOTER_SUBMISSION.strip()[:79]
    window.addstr.assert_any_call(0, 0, text.encode('utf-8'))

    # Submission
    submission_data = page.content.get(-1)
    text = submission_data['title'].encode('utf-8')
    window.subwin.addstr.assert_any_call(1, 1, text, 2097152)
    assert window.subwin.border.called

    # Comment
    comment_data = page.content.get(0)
    text = comment_data['split_body'][0].encode('utf-8')
    window.subwin.addstr.assert_any_call(1, 1, text, curses.A_NORMAL)

    # More Comments
    comment_data = page.content.get(1)
    text = comment_data['body'].encode('utf-8')
    window.subwin.addstr.assert_any_call(0, 1, text, curses.A_NORMAL)

    # Cursor should not be drawn when the page is first opened
    assert not any(args[0][3] == curses.A_REVERSE
                   for args in window.subwin.addch.call_args_list)

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


def test_submission_refresh(submission_page):

    # Should be able to refresh content
    submission_page.refresh_content()


def test_submission_exit(submission_page):

    # Exiting should set active to false
    submission_page.active = True
    submission_page.controller.trigger('h')
    assert not submission_page.active


def test_submission_unauthenticated(submission_page, terminal):

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


def test_submission_open(submission_page, terminal):

    # Open the selected link with the web browser
    with mock.patch.object(terminal, 'open_browser'):
        submission_page.controller.trigger(terminal.RETURN)
        assert terminal.open_browser.called


def test_submission_prompt(submission_page, terminal):

    # Prompt for a different subreddit
    with mock.patch.object(terminal, 'prompt_input'):
        # Valid input
        submission_page.active = True
        submission_page.selected_page = None
        terminal.prompt_input.return_value = 'front/top'
        submission_page.controller.trigger('/')

        submission_page.handle_selected_page()
        assert not submission_page.active
        assert submission_page.selected_page

        # Invalid input
        submission_page.active = True
        submission_page.selected_page = None
        terminal.prompt_input.return_value = 'front/pot'
        submission_page.controller.trigger('/')

        submission_page.handle_selected_page()
        assert submission_page.active
        assert not submission_page.selected_page


@pytest.mark.parametrize('prompt', PROMPTS.values(), ids=list(PROMPTS))
def test_submission_prompt_submission(submission_page, terminal, prompt):

    # Navigate to a different submission from inside a submission
    with mock.patch.object(terminal, 'prompt_input'):
        terminal.prompt_input.return_value = prompt
        submission_page.content.order = 'top'
        submission_page.controller.trigger('/')
        assert not terminal.loader.exception

        submission_page.handle_selected_page()
        assert not submission_page.active
        assert submission_page.selected_page

        assert submission_page.selected_page.content.order is None
        data = submission_page.selected_page.content.get(-1)
        assert data['object'].id == '571dw3'


def test_submission_order(submission_page):

    submission_page.controller.trigger('1')
    assert submission_page.content.order == 'hot'
    submission_page.controller.trigger('2')
    assert submission_page.content.order == 'top'
    submission_page.controller.trigger('3')
    assert submission_page.content.order == 'rising'
    submission_page.controller.trigger('4')
    assert submission_page.content.order == 'new'
    submission_page.controller.trigger('5')
    assert submission_page.content.order == 'controversial'

    # Shouldn't be able to sort the submission page by gilded
    submission_page.controller.trigger('6')
    assert submission_page.content.order == 'controversial'


def test_submission_move_top_bottom(submission_page):

    submission_page.controller.trigger('G')
    assert submission_page.nav.absolute_index == 44

    submission_page.controller.trigger('g')
    submission_page.controller.trigger('g')
    assert submission_page.nav.absolute_index == -1


def test_submission_move_sibling_parent(submission_page):

    # Jump to sibling
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')
        submission_page.controller.trigger('J')
    assert submission_page.nav.absolute_index == 7

    # Jump to parent
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('k')
        submission_page.controller.trigger('k')
        submission_page.controller.trigger('K')
    assert submission_page.nav.absolute_index == 0


def test_submission_pager(submission_page, terminal):

    # View a submission with the pager
    with mock.patch.object(terminal, 'open_pager'):
        submission_page.controller.trigger('l')
        assert terminal.open_pager.called

    # Move down to the first comment
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')

    # View a comment with the pager
    with mock.patch.object(terminal, 'open_pager'):
        submission_page.controller.trigger('l')
        assert terminal.open_pager.called


def test_submission_comment_not_enough_space(submission_page, terminal):

    # The first comment is 10 lines, shrink the screen so that it won't fit.
    # Setting the terminal to 10 lines means that there will only be 8 lines
    # available (after subtracting the header and footer) to draw the comment.
    terminal.stdscr.nlines = 10

    # Select the first comment
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.move_cursor_down()

    submission_page.draw()

    text = '(Not enough space to display)'.encode('ascii')
    window = terminal.stdscr.subwin
    window.subwin.addstr.assert_any_call(6, 1, text, curses.A_NORMAL)


def test_submission_vote(submission_page, refresh_token):

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

    # Test voting on the submission
    with mock.patch('tuir.packages.praw.objects.Submission.upvote') as upvote,            \
            mock.patch('tuir.packages.praw.objects.Submission.downvote') as downvote,     \
            mock.patch('tuir.packages.praw.objects.Submission.clear_vote') as clear_vote:

        data = submission_page.get_selected_item()
        data['object'].archived = False

        # Upvote
        submission_page.controller.trigger('a')
        assert upvote.called
        assert data['likes'] is True

        # Clear vote
        submission_page.controller.trigger('a')
        assert clear_vote.called
        assert data['likes'] is None

        # Upvote
        submission_page.controller.trigger('a')
        assert upvote.called
        assert data['likes'] is True

        # Downvote
        submission_page.controller.trigger('z')
        assert downvote.called
        assert data['likes'] is False

        # Clear vote
        submission_page.controller.trigger('z')
        assert clear_vote.called
        assert data['likes'] is None

        # Upvote - exception
        upvote.side_effect = KeyboardInterrupt
        submission_page.controller.trigger('a')
        assert data['likes'] is None

        # Downvote - exception
        downvote.side_effect = KeyboardInterrupt
        submission_page.controller.trigger('a')
        assert data['likes'] is None


def test_submission_vote_archived(submission_page, refresh_token, terminal):

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

    # Load an archived submission
    archived_url = 'https://www.reddit.com/r/IAmA/comments/z1c9z/'
    submission_page.refresh_content(name=archived_url)

    with mock.patch.object(terminal, 'show_notification') as show_notification:
        data = submission_page.get_selected_item()

        # Upvote the submission
        show_notification.reset_mock()
        submission_page.controller.trigger('a')
        show_notification.assert_called_with('Voting disabled for archived post', style='Error')
        assert data['likes'] is None

        # Downvote the submission
        show_notification.reset_mock()
        submission_page.controller.trigger('z')
        show_notification.assert_called_with('Voting disabled for archived post', style='Error')
        assert data['likes'] is None


def test_submission_save(submission_page, refresh_token):

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

    # Test save on the submission
    with mock.patch('tuir.packages.praw.objects.Submission.save') as save,        \
            mock.patch('tuir.packages.praw.objects.Submission.unsave') as unsave:

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

        # Save
        submission_page.controller.trigger('w')
        assert save.called
        assert data['saved'] is True

        # Unsave
        submission_page.controller.trigger('w')
        assert unsave.called
        assert data['saved'] is False

        # Save - exception
        save.side_effect = KeyboardInterrupt
        submission_page.controller.trigger('w')
        assert data['saved'] is False


def test_submission_comment_save(submission_page, terminal, refresh_token):

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

    # Move down to the first comment
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')

    # Test save on the comment submission
    with mock.patch('tuir.packages.praw.objects.Comment.save') as save,        \
            mock.patch('tuir.packages.praw.objects.Comment.unsave') as unsave:

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

        # Save
        submission_page.controller.trigger('w')
        assert save.called
        assert data['saved'] is True

        # Unsave
        submission_page.controller.trigger('w')
        assert unsave.called
        assert data['saved'] is False

        # Save - exception
        save.side_effect = KeyboardInterrupt
        submission_page.controller.trigger('w')
        assert data['saved'] is False


def test_submission_comment(submission_page, terminal, refresh_token):

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

    # Leave a comment
    with mock.patch('tuir.packages.praw.objects.Submission.add_comment') as add_comment, \
            mock.patch.object(terminal, 'open_editor') as open_editor,                  \
            mock.patch('time.sleep'):
        open_editor.return_value.__enter__.return_value = 'comment text'
        submission_page.controller.trigger('c')
        assert open_editor.called
        add_comment.assert_called_with('comment text')


def test_submission_delete(submission_page, terminal, refresh_token):

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

    # Can't delete the submission
    curses.flash.reset_mock()
    submission_page.controller.trigger('d')
    assert curses.flash.called

    # Move down to the first comment
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')

    # Try to delete the first comment - wrong author
    curses.flash.reset_mock()
    submission_page.controller.trigger('d')
    assert curses.flash.called

    # Spoof the author and try to delete again
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['author'] = submission_page.reddit.user.name
    with mock.patch('tuir.packages.praw.objects.Comment.delete') as delete,  \
            mock.patch.object(terminal.stdscr, 'getch') as getch,           \
            mock.patch('time.sleep'):
        getch.return_value = ord('y')
        submission_page.controller.trigger('d')
        assert delete.called


def test_submission_edit(submission_page, terminal, refresh_token):

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

    # Try to edit the submission - wrong author
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['author'] = 'some other person'
    curses.flash.reset_mock()
    submission_page.controller.trigger('e')
    assert curses.flash.called

    # Spoof the submission and submit empty comment
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['author'] = submission_page.reddit.user.name
    with mock.patch('tuir.packages.praw.objects.Submission.edit') as edit,  \
            mock.patch.object(terminal, 'open_editor') as open_editor,     \
            mock.patch.object(terminal, 'show_notification'),  \
            mock.patch('time.sleep'):
        open_editor.return_value.__enter__.return_value = ''

        submission_page.controller.trigger('e')
        assert open_editor.called
        terminal.show_notification.assert_called_with('Canceled')
        edit.assert_not_called()

    # Spoof the submission and try to edit again
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['author'] = submission_page.reddit.user.name
    with mock.patch('tuir.packages.praw.objects.Submission.edit') as edit,  \
            mock.patch.object(terminal, 'open_editor') as open_editor,     \
            mock.patch('time.sleep'):
        open_editor.return_value.__enter__.return_value = 'submission text'

        submission_page.controller.trigger('e')
        assert open_editor.called
        edit.assert_called_with('submission text')

    # Move down to the first comment
    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')

    # Spoof the author and edit the comment
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['author'] = submission_page.reddit.user.name
    with mock.patch('tuir.packages.praw.objects.Comment.edit') as edit, \
            mock.patch.object(terminal, 'open_editor') as open_editor, \
            mock.patch('time.sleep'):
        open_editor.return_value.__enter__.return_value = 'comment text'

        submission_page.controller.trigger('e')
        assert open_editor.called
        edit.assert_called_with('comment text')


def test_submission_urlview(submission_page, terminal, refresh_token):

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

    # Submission case
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['body'] = 'test comment body  ❤'
    with mock.patch.object(terminal, 'open_urlview') as open_urlview:
        submission_page.controller.trigger('b')
        open_urlview.assert_called_with('test comment body  ❤')

    # Subreddit case
    data = submission_page.content.get(submission_page.nav.absolute_index)
    data['text'] = ''
    data['body'] = ''
    data['url_full'] = 'http://test.url.com  ❤'
    with mock.patch.object(terminal, 'open_urlview') as open_urlview, \
            mock.patch('subprocess.Popen'):
        submission_page.controller.trigger('b')
        open_urlview.assert_called_with('http://test.url.com  ❤')


def test_submission_prompt_and_select_link(submission_page, terminal):

    # A link submission should return the URL that it's pointing to
    link = submission_page.prompt_and_select_link()
    assert link == 'https://github.com/michael-lazar/rtv'

    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')

    # The first comment doesn't have any links in the comment body
    link = submission_page.prompt_and_select_link()
    data = submission_page.get_selected_item()
    assert link == data['permalink']

    with mock.patch.object(submission_page, 'clear_input_queue'):
        submission_page.controller.trigger('j')

    # The second comment has a link embedded in the comment body, and
    # the user is prompted to select which link to open
    with mock.patch.object(terminal, 'prompt_user_to_select_link') as prompt:
        prompt.return_value = 'https://selected_link'

        link = submission_page.prompt_and_select_link()
        data = submission_page.get_selected_item()

        assert link == prompt.return_value

        embedded_url = 'http://peterdowns.com/posts/first-time-with-pypi.html'
        assert prompt.call_args[0][0] == [
            {'text': 'Permalink', 'href': data['permalink']},
            {'text': 'Relevant tutorial', 'href': embedded_url}
        ]

    submission_page.controller.trigger(' ')

    # The comment is now hidden so there are no links to select
    link = submission_page.prompt_and_select_link()
    assert link is None


def test_submission_copy_permalink(submission_page, terminal):
    url = ('https://www.reddit.com/r/Python/comments/2xmo63/'
           'a_python_terminal_viewer_for_browsing_reddit/')
    with mock.patch('tuir.page.clipboard_copy') as copy, \
            mock.patch.object(terminal, 'show_notification'):
        submission_page.controller.trigger('y')
        copy.assert_called_with(url, mock.ANY)
        terminal.show_notification.assert_called_with(['Copied to clipboard:', url], timeout=1)


def test_submission_copy_link(submission_page, terminal):
    url = 'https://github.com/michael-lazar/rtv'
    with mock.patch('tuir.page.clipboard_copy') as copy, \
            mock.patch.object(terminal, 'prompt_user_to_select_link') as prompt, \
            mock.patch.object(terminal, 'show_notification'):
        submission_page.controller.trigger('Y')
        copy.assert_called_with(url, mock.ANY)
        terminal.show_notification.assert_called_with(['Copied to clipboard:', url], timeout=1)
