"""
Tests for the TextBuffer class
"""
from pytest import fixture, raises

from poezio.text_buffer import (
    CorrectionError,
    DeletionError,
    TextBuffer,
)
from poezio.user import User

from poezio.ui.types import (
    Message,
    MucJoin,
    MucLeave,
    BaseMessage,
    MucOwnJoinMessage,
    MucOwnLeaveMessage,
)
from poezio.xhtml import clean_text

from slixmpp import JID


@fixture(scope='function')
def buf2048():
    return TextBuffer(2048)


@fixture(scope='function')
def msgs_nojoin():
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's')
    leave = MucOwnLeaveMessage('leave')
    return [msg1, msg2, leave]


@fixture(scope='function')
def msgs_noleave():
    join = MucOwnJoinMessage('join')
    msg3 = Message('3', 'd')
    msg4 = Message('4', 'f')
    return [join, msg3, msg4]


@fixture(scope='function')
def msgs_doublejoin():
    join = MucOwnJoinMessage('join')
    msg1 = Message('1', 'd')
    msg2 = Message('2', 'f')
    join2 = MucOwnJoinMessage('join')
    return [join, msg1, msg2, join2]


@fixture(scope='function')
def msgs_join_leave_nomsg():
    msg1 = Message('1', 'd')
    msg2 = Message('2', 'f')
    join = MucOwnJoinMessage('join')
    leave = MucOwnLeaveMessage('leave')
    join2 = MucOwnJoinMessage('join')
    return [msg1, msg2, join, leave, join2]


def test_last_message(buf2048):
    msg = BaseMessage('toto')
    buf2048.add_message(BaseMessage('titi'))
    buf2048.add_message(msg)
    assert buf2048.last_message is msg


def test_message_nb_limit():
    buf = TextBuffer(5)
    for i in range(10):
        buf.add_message(BaseMessage("%s" % i))
    assert len(buf.messages) == 5


def test_find_gap(buf2048, msgs_noleave):
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's')
    leave = MucOwnLeaveMessage('leave')
    join = MucOwnJoinMessage('join')
    msg3 = Message('3', 'd')
    msg4 = Message('4', 'f')
    msgs = [msg1, msg2, leave, join, msg3, msg4]
    for msg in msgs:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert gap.leave_message == leave
    assert gap.join_message == join
    assert gap.last_timestamp_before_leave == msg2.time
    assert gap.first_timestamp_after_join == msg3.time


def test_find_gap_leavejoin(buf2048, msgs_join_leave_nomsg):
    for msg in msgs_join_leave_nomsg:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert gap.leave_message == msgs_join_leave_nomsg[3]
    assert gap.join_message == msgs_join_leave_nomsg[4]
    assert gap.last_timestamp_before_leave == msgs_join_leave_nomsg[3].time


def test_find_gap_doublejoin(buf2048, msgs_doublejoin):
    for msg in msgs_doublejoin:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert gap.leave_message == msgs_doublejoin[2]
    assert gap.join_message == msgs_doublejoin[3]


def test_find_gap_doublejoin_no_msg(buf2048):
    join1 = MucOwnJoinMessage('join')
    join2 = MucOwnJoinMessage('join')
    for msg in [join1, join2]:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert gap.leave_message is join1
    assert gap.join_message is join2


def test_find_gap_already_filled(buf2048):
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's')
    leave = MucOwnLeaveMessage('leave')
    msg5 = Message('5', 'g')
    msg6 = Message('6', 'h')
    join = MucOwnJoinMessage('join')
    msg3 = Message('3', 'd')
    msg4 = Message('4', 'f')
    msgs = [msg1, msg2, leave, msg5, msg6, join, msg3, msg4]
    for msg in msgs:
        buf2048.add_message(msg)
    assert buf2048.find_last_gap_muc() is None


def test_find_gap_noleave(buf2048, msgs_noleave):
    for msg in msgs_noleave:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert gap.leave_message is None
    assert gap.last_timestamp_before_leave is None
    assert gap.join_message == msgs_noleave[0]
    assert gap.first_timestamp_after_join == msgs_noleave[1].time


def test_find_gap_nojoin(buf2048, msgs_nojoin):
    for msg in msgs_nojoin:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert gap.leave_message == msgs_nojoin[-1]
    assert gap.join_message is None
    assert gap.last_timestamp_before_leave == msgs_nojoin[1].time


def test_get_gap_index(buf2048):
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's')
    leave = MucOwnLeaveMessage('leave')
    join = MucOwnJoinMessage('join')
    msg3 = Message('3', 'd')
    msg4 = Message('4', 'f')
    msgs = [msg1, msg2, leave, join, msg3, msg4]
    for msg in msgs:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert buf2048.get_gap_index(gap) == 3


def test_get_gap_index_doublejoin(buf2048, msgs_doublejoin):
    for msg in msgs_doublejoin:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert buf2048.get_gap_index(gap) == 3


def test_get_gap_index_doublejoin_no_msg(buf2048):
    join1 = MucOwnJoinMessage('join')
    join2 = MucOwnJoinMessage('join')
    for msg in [join1, join2]:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert buf2048.get_gap_index(gap) == 1


def test_get_gap_index_nojoin(buf2048, msgs_nojoin):
    for msg in msgs_nojoin:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert buf2048.get_gap_index(gap) == 3


def test_get_gap_index_noleave(buf2048, msgs_noleave):
    for msg in msgs_noleave:
        buf2048.add_message(msg)
    gap = buf2048.find_last_gap_muc()
    assert buf2048.get_gap_index(gap) == 0


def test_add_history_messages(buf2048):
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's')
    leave = MucOwnLeaveMessage('leave')
    join = MucOwnJoinMessage('join')
    msg3 = Message('3', 'd')
    msg4 = Message('4', 'f')
    msgs = [msg1, msg2, leave, join, msg3, msg4]
    for msg in msgs:
        buf2048.add_message(msg)
    msg5 = Message('5', 'g')
    msg6 = Message('6', 'h')
    gap = buf2048.find_last_gap_muc()
    buf2048.add_history_messages([msg5, msg6], gap=gap)
    assert buf2048.messages == [msg1, msg2, leave, msg5, msg6, join, msg3, msg4]


def test_add_history_empty(buf2048):
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's')
    msg3 = Message('3', 'd')
    msg4 = Message('4', 'f')
    buf2048.add_message(msg1)
    buf2048.add_history_messages([msg2, msg3, msg4])
    assert buf2048.messages == [msg2, msg3, msg4, msg1]


def test_modify_message(buf2048):
    user = User("toto", "owner", "", "xa", "moderator", JID("toto@titi"), color="red")
    msg1 = Message('1', 'q')
    msg2 = Message('2', 's', identifier="2", jid=JID("a@b"))
    msg3 = Message('3', 'd', identifier="3", user=user)
    msg4 = Message('4', 'f')
    msgs = [msg1, msg2, msg3, msg4]
    for msg in msgs:
        buf2048.add_message(msg)

    buf2048.modify_message("coucou", "2", "repl", jid=JID("a@b"))
    assert buf2048.messages[0].txt == "1"
    assert buf2048.messages[1].txt == "coucou"
    assert buf2048.messages[2].txt == "3"
    assert buf2048.messages[3].txt == "4"

    buf2048.modify_message("coucou 2", "3", "repl2", user=user)
    assert buf2048.messages[0].txt == "1"
    assert buf2048.messages[1].txt == "coucou"
    assert buf2048.messages[2].txt == "coucou 2"
    assert buf2048.messages[3].txt == "4"

    buf2048.modify_message("coucou 3", "repl", "repl3", jid=JID("a@b"))
    assert buf2048.messages[0].txt == "1"
    assert buf2048.messages[1].txt == "coucou 3"
    assert buf2048.messages[2].txt == "coucou 2"
    assert buf2048.messages[3].txt == "4"

    with raises(CorrectionError):
        buf2048.modify_message("coucou 4", "missing", "repl4", jid=JID("a@b"))

    with raises(CorrectionError):
        buf2048.modify_message("coucou 4", "repl3", "repl4", user=user)

    with raises(CorrectionError):
        buf2048.modify_message("coucou 4", "repl3", "repl4")


def test_delete_message(buf2048):
    user = User("toto", "owner", "", "xa", "moderator", JID("toto@titi"), color="red")
    user2 = User("titi", "user", "", "xa", "moderator", JID("titi@toto"), color="blue")
    msg1 = Message('1', 'q', identifier="1")
    msg2 = Message('2', 's', identifier="2", jid=JID("a@b"))
    msg3 = Message('3', 'd', identifier="3", user=user)
    msg4 = Message('4', 'f', identifier="4")
    msg5 = Message('5', 'g', identifier="5")
    msg6 = Message('6', 'h', identifier="6")
    msgs = [msg1, msg2, msg3, msg4, msg5, msg6]
    for msg in msgs:
        buf2048.add_message(msg)

    buf2048.delete_message("4", moderation=True)
    assert buf2048.messages[3].txt == "5"

    buf2048.delete_message("5", moderation=True, tombstone="deleted by toto")
    assert "deleted by toto" in buf2048.messages[3].txt

    with raises(DeletionError):
        buf2048.delete_message("2", jid=JID("c@d"))

    with raises(DeletionError):
        buf2048.delete_message("3", user=user2)

    with raises(DeletionError):
        buf2048.delete_message("3")

    assert not buf2048.delete_message("none")

    buf2048.delete_message("2", jid=JID("a@b"))
    assert buf2048.messages[1].txt == "3"

    buf2048.delete_message("3", user=user)
    assert "deleted by" in buf2048.messages[1].txt


def test_reactions(buf2048):
    buf2048.add_message(Message('body', 'nick', identifier="0"))
    buf2048.add_message(Message('body2', 'nick2', identifier="1"))
    buf2048.add_message(Message('body3', 'nick3', identifier="2"))
    assert buf2048.react(to='1', by='toto', reactions={'a', 'b'})
    assert buf2048.react(to='1', by='titi', reactions={'a', 'b', 'c'})
    assert buf2048.react(to='1', by='tata', reactions={'a', 'b', 'c', 'd'})

    assert buf2048.messages[1].reactions['a'] == {'toto', 'titi', 'tata'}
    assert buf2048.messages[1].reactions['b'] == {'toto', 'titi', 'tata'}
    assert buf2048.messages[1].reactions['c'] == {'titi', 'tata'}

    # Remove a reaction now
    assert buf2048.react(to='1', by='tata', reactions={'a', 'b'})

    assert buf2048.messages[1].reactions['a'] == {'toto', 'titi', 'tata'}
    assert buf2048.messages[1].reactions['b'] == {'toto', 'titi', 'tata'}
    assert buf2048.messages[1].reactions['c'] == {'titi'}

    # Message not found
    assert not buf2048.react(to='5', by='tata', reactions={'p'})
    # No change
    assert not buf2048.react(to='1', by='tata', reactions={'a', 'b'})


def test_filters(buf2048):
    buf2048.add_message(MucLeave("tutu"))
    buf2048.add_message(Message('body', 'nick', identifier="0"))
    buf2048.add_message(MucJoin("toto"))
    buf2048.add_message(Message('body2', 'nick2', identifier="1"))
    buf2048.add_message(MucLeave("tata"))
    buf2048.add_message(Message('body3', 'nick3', identifier="2"))
    buf2048.add_message(MucJoin("titi"))

    buf2048.filter_classes((MucLeave, MucJoin))
    filtered = buf2048.filter_messages()
    assert len(filtered) == 3
    assert filtered[0].txt == 'body'
    assert filtered[1].txt == 'body2'
    assert filtered[2].txt == 'body3'

    buf2048.filter_classes((MucLeave,))
    filtered = buf2048.filter_messages()
    assert len(filtered) == 5
    assert filtered[0].txt == 'body'
    assert clean_text(filtered[1].txt) == 'toto'
    assert filtered[2].txt == 'body2'
    assert filtered[3].txt == 'body3'
    assert clean_text(filtered[4].txt) == 'titi'

    buf2048.filter_classes((MucJoin,))
    filtered = buf2048.filter_messages()
    assert len(filtered) == 5
    assert clean_text(filtered[0].txt) == 'tutu'
    assert filtered[1].txt == 'body'
    assert filtered[2].txt == 'body2'
    assert clean_text(filtered[3].txt) == 'tata'
    assert filtered[4].txt == 'body3'
