from maps.wikimap.stat.libs.feedback.lib.paid_operations import (
    PAID_OPERATIONS,
    _OPERATION_ACCEPT,
    _OPERATION_NEED_INFO,
    _OPERATION_REJECT,
    _VIRTUAL_OPERATION_ACCEPT_WITH_EDIT,
    _commit_ids_to_commits_created_at,
    _get_payable_operations_from_paid,
    _is_accept_with_edit,
    _unwrap_history,
    get_paid_operations_at_date,
    get_task_id,
)

from maps.wikimap.stat.libs import nile_ut

from nile.api.v1 import Record
from yt.yson import to_yson_type

import pytest


def test_should_unwrap_history():
    result = _unwrap_history([
        Record(history=to_yson_type([
            {b'operation': b'operation 11', b'modifiedBy': 11, b'modifiedAt': b'2021-11-30 11:11:11+03:00'},
            {b'operation': b'operation 12', b'modifiedBy': 12, b'modifiedAt': b'2021-11-30 12:12:12+03:00'}
        ]), test_column=1),
        Record(history=to_yson_type([
            {b'operation': b'operation 2', b'modifiedBy': 2, b'modifiedAt': b'2021-11-30 02:02:02+03:00'},
        ]), test_column=2),
    ])

    assert sorted([
        Record(operation=b'operation 11', modified_by=11, modified_at=b'2021-11-30T11:11:11+03:00', test_column=1),
        Record(operation=b'operation 12', modified_by=12, modified_at=b'2021-11-30T12:12:12+03:00', test_column=1),
        Record(operation=b'operation 2',  modified_by=2,  modified_at=b'2021-11-30T02:02:02+03:00', test_column=2),
    ]) == sorted(result)


def test_should_convert_datetime_to_iso_formatxo_when_unwrap_history():
    result = _unwrap_history([
        Record(
            history=to_yson_type([
                {b'operation': b'operation 1', b'modifiedBy': 11, b'modifiedAt': b'2020-01-25 01:01:01.2345+01'},
                {b'operation': b'operation 2', b'modifiedBy': 12, b'modifiedAt': b'2020-01-25 02:02:02'},
            ]),
        ),
    ])

    assert sorted([
        Record(operation=b'operation 1', modified_by=11, modified_at=b'2020-01-25T01:01:01+01:00'),
        Record(operation=b'operation 2', modified_by=12, modified_at=b'2020-01-25T02:02:02+03:00'),
    ]) == sorted(result)


def test_should_convert_commit_ids_to_commits_created_at():
    result = nile_ut.yt_run(
        _commit_ids_to_commits_created_at,
        stream=nile_ut.Table([
            Record(id=1, position=b'pos 1', source=b'src 1', type=b'type 1', operation=b'op 1', modified_by=10, modified_at=b'2021-11-11',
                   commit_ids=to_yson_type([11, 12])),
            Record(id=2, position=b'pos 2', source=b'src 2', type=b'type 2', operation=b'op 2', modified_by=20, modified_at=b'2021-11-12',
                   commit_ids=to_yson_type([20])),
            Record(id=3, position=b'pos 3', source=b'src 3', type=b'type 3', operation=b'op 3', modified_by=30, modified_at=b'2021-11-13',
                   commit_ids=to_yson_type([])),
        ]),
        social_commit_event=nile_ut.Table([
            Record(commit_id=11, created_at=b'2021-11-11', type=b'edit', social_commit_event_other_column=11),
            Record(commit_id=12, created_at=b'2021-11-12', type=b'edit', social_commit_event_other_column=12),
            Record(commit_id=20, created_at=b'2021-11-20', type=b'edit', social_commit_event_other_column=20),
        ])
    )

    assert sorted([
        Record(id=1, position=b'pos 1', source=b'src 1', type=b'type 1', operation=b'op 1', modified_by=10, modified_at=b'2021-11-11',
               commits_created_at=[b'2021-11-11T00:00:00+03:00', b'2021-11-12T00:00:00+03:00']),
        Record(id=2, position=b'pos 2', source=b'src 2', type=b'type 2', operation=b'op 2', modified_by=20, modified_at=b'2021-11-12',
               commits_created_at=[b'2021-11-20T00:00:00+03:00']),
        Record(id=3, position=b'pos 3', source=b'src 3', type=b'type 3', operation=b'op 3', modified_by=30, modified_at=b'2021-11-13',
               commits_created_at=[]),
    ]) == sorted(result)


def test_should_convert_datetime_to_iso_format_when_convert_commit_ids_to_commits_created_at():
    result = nile_ut.yt_run(
        _commit_ids_to_commits_created_at,
        stream=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', operation=b'op', modified_by=10, modified_at=b'2021-11-11',
                   commit_ids=to_yson_type([1, 2])),
        ]),
        social_commit_event=nile_ut.Table([
            Record(commit_id=1, created_at=b'2020-01-25 01:01:01.2345+01', type=b'edit'),
            Record(commit_id=2, created_at=b'2020-01-25 02:02:02',         type=b'edit'),
        ])
    )

    assert sorted([
        Record(id=1, position=b'pos', source=b'src', type=b'type', operation=b'op', modified_by=10, modified_at=b'2021-11-11',
               commits_created_at=[b'2020-01-25T01:01:01+01:00', b'2020-01-25T02:02:02+03:00']),
    ]) == sorted(result)


def test_should_use_edits_only_when_convert_commit_id_to_commits_created_at():
    result = nile_ut.yt_run(
        _commit_ids_to_commits_created_at,
        stream=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', operation=b'op', modified_by=10, modified_at=b'2021-11-30',
                   commit_ids=to_yson_type([1])),
        ]),
        social_commit_event=nile_ut.Table([
            Record(commit_id=1, created_at=b'2021-11-29T00:00:00+03:00', type=b'non-edit'),
            Record(commit_id=1, created_at=b'2021-11-30T00:00:00+03:00', type=b'edit'),
            Record(commit_id=1, created_at=b'2021-12-01T00:00:00+03:00', type=b'non-edit'),
        ])
    )

    assert sorted([
        Record(id=1, position=b'pos', source=b'src', type=b'type', operation=b'op', modified_by=10, modified_at=b'2021-11-30',
               commits_created_at=[b'2021-11-30T00:00:00+03:00']),
    ]) == sorted(result)


def test_should_convert_accept_to_accept_with_edit():
    assert _is_accept_with_edit(
        _OPERATION_ACCEPT,
        modified_at='2021-11-30',
        prev_op_modified_at='1970-01-01',
        commits_created_at=['2021-12-01', '2021-11-30']
    )


def test_should_not_convert_accept_to_accept_with_edit():
    # modified_at before commits_created_at
    assert not _is_accept_with_edit(
        _OPERATION_ACCEPT,
        modified_at='2021-11-30',
        prev_op_modified_at='1970-01-01',
        commits_created_at=['2021-12-01']
    )

    # commit is related to a previous operation
    assert not _is_accept_with_edit(
        _OPERATION_ACCEPT,
        modified_at='2021-11-30',
        prev_op_modified_at='2021-11-29',
        commits_created_at=['2021-11-28']
    )

    # commit made after operation
    assert not _is_accept_with_edit(
        _OPERATION_ACCEPT,
        modified_at='2021-11-30',
        prev_op_modified_at='1970-01-01',
        commits_created_at=['2021-12-01']
    )


@pytest.mark.parametrize('operation', PAID_OPERATIONS - {_OPERATION_ACCEPT, _VIRTUAL_OPERATION_ACCEPT_WITH_EDIT})
def test_should_not_convert_non_accept_operation_to_accept_with_edit(operation):
    assert not _is_accept_with_edit(
        operation,
        modified_at='2021-11-30',
        prev_op_modified_at='1970-01-01',
        commits_created_at=[]
    )


def test_should_not_convert_accept_operation_without_commits_to_accept_with_edit():
    assert not _is_accept_with_edit(
        _OPERATION_ACCEPT,
        modified_at='2021-11-30',
        prev_op_modified_at='1970-01-01',
        commits_created_at=[]
    )


def test_should_fail_getting_payable_operation_from_non_paid():
    from nile.drivers.common.progress import CommandFailedError

    with pytest.raises(CommandFailedError, match="Non-paid operation"):
        nile_ut.yt_run(
            _get_payable_operations_from_paid,
            stream=nile_ut.Table([
                Record(id=1, modified_at=b'2021-11-30T01:01:01', commits_created_at=[], operation=b'non-paid operation'),
            ])
        )


@pytest.mark.parametrize('operation', PAID_OPERATIONS - {_OPERATION_NEED_INFO, _VIRTUAL_OPERATION_ACCEPT_WITH_EDIT})
def test_should_pay_for_first_non_need_info_operation_only(operation):
    result = nile_ut.yt_run(
        _get_payable_operations_from_paid,
        stream=nile_ut.Table([
            Record(id=1, modified_at=b'2021-11-30T01:01:01', commits_created_at=[], operation=operation, other_column=1),
            Record(id=1, modified_at=b'2021-11-30T02:02:02', commits_created_at=[], operation=operation, other_column=2),
        ])
    )

    assert sorted([
        Record(modified_at=b'2021-11-30T01:01:01', operation=operation, other_column=1),
    ]) == sorted(result)


@pytest.mark.parametrize('operation', PAID_OPERATIONS - {_OPERATION_NEED_INFO, _VIRTUAL_OPERATION_ACCEPT_WITH_EDIT})
def test_should_pay_for_first_operation_after_need_info(operation):
    result = nile_ut.yt_run(
        _get_payable_operations_from_paid,
        stream=nile_ut.Table([
            Record(id=1, modified_at=b'2021-11-30T01:01:01', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=1),
            Record(id=1, modified_at=b'2021-11-30T02:02:02', commits_created_at=[], operation=operation,            other_column=2),
            Record(id=1, modified_at=b'2021-11-30T03:03:03', commits_created_at=[], operation=operation,            other_column=3),
        ])
    )

    assert sorted([
        Record(modified_at=b'2021-11-30T01:01:01', operation=_OPERATION_NEED_INFO, other_column=1),
        Record(modified_at=b'2021-11-30T02:02:02', operation=operation,            other_column=2),
    ]) == sorted(result)


@pytest.mark.parametrize('operation', PAID_OPERATIONS - {_OPERATION_NEED_INFO, _VIRTUAL_OPERATION_ACCEPT_WITH_EDIT})
def test_should_pay_for_all_need_info_before_other(operation):
    result = nile_ut.yt_run(
        _get_payable_operations_from_paid,
        stream=nile_ut.Table([
            Record(id=1, modified_at=b'2021-11-30T01:01:01', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=1),
            Record(id=1, modified_at=b'2021-11-30T02:02:02', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=2),
            Record(id=1, modified_at=b'2021-11-30T03:03:03', commits_created_at=[], operation=operation,            other_column=3),
            Record(id=1, modified_at=b'2021-11-30T04:04:04', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=4),
        ])
    )

    assert sorted([
        Record(modified_at=b'2021-11-30T01:01:01', operation=_OPERATION_NEED_INFO, other_column=1),
        Record(modified_at=b'2021-11-30T02:02:02', operation=_OPERATION_NEED_INFO, other_column=2),
        Record(modified_at=b'2021-11-30T03:03:03', operation=operation,            other_column=3),
    ]) == sorted(result)


def test_should_pay_for_events_separately():
    result = nile_ut.yt_run(
        _get_payable_operations_from_paid,
        stream=nile_ut.Table([
            Record(id=1, modified_at=b'2021-11-30T10:10:11', commits_created_at=[], operation=_OPERATION_ACCEPT,    other_column=11),
            Record(id=1, modified_at=b'2021-11-30T10:10:12', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=12),
            Record(id=2, modified_at=b'2021-11-30T20:20:23', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=21),
            Record(id=2, modified_at=b'2021-11-30T20:20:24', commits_created_at=[], operation=_OPERATION_REJECT,    other_column=22),
        ])
    )

    assert sorted([
        Record(modified_at=b'2021-11-30T10:10:11', operation=_OPERATION_ACCEPT,    other_column=11),
        Record(modified_at=b'2021-11-30T20:20:23', operation=_OPERATION_NEED_INFO, other_column=21),
        Record(modified_at=b'2021-11-30T20:20:24', operation=_OPERATION_REJECT,    other_column=22),
    ]) == sorted(result)


def test_should_pay_for_events_sorted_by_modified_at():
    result = nile_ut.yt_run(
        _get_payable_operations_from_paid,
        stream=nile_ut.Table([
            Record(id=1, modified_at=b'2021-11-30T00:00:03', commits_created_at=[], operation=_OPERATION_ACCEPT,    other_column=1),
            Record(id=1, modified_at=b'2021-11-30T00:00:01', commits_created_at=[], operation=_OPERATION_NEED_INFO, other_column=2),
            Record(id=1, modified_at=b'2021-11-30T00:00:02', commits_created_at=[], operation=_OPERATION_ACCEPT,    other_column=3),
        ])
    )

    assert sorted([
        Record(modified_at=b'2021-11-30T00:00:01', operation=_OPERATION_NEED_INFO, other_column=2),
        Record(modified_at=b'2021-11-30T00:00:02', operation=_OPERATION_ACCEPT,    other_column=3),
    ]) == sorted(result)


def test_should_distinguish_accept_operations_by_commits_presence():
    result = nile_ut.yt_run(
        _get_payable_operations_from_paid,
        stream=nile_ut.Table([
            # No commits
            Record(id=1, modified_at=b'2021-11-30T00:10:00', commits_created_at=[],                       operation=_OPERATION_ACCEPT, other_column=1),
            # Commits has been created before or at the same time with operation.
            Record(id=2, modified_at=b'2021-11-30T00:10:00', commits_created_at=[b'2021-11-30T00:09:00'], operation=_OPERATION_ACCEPT, other_column=2),
            Record(id=3, modified_at=b'2021-11-30T00:10:00', commits_created_at=[b'2021-11-30T00:10:00'], operation=_OPERATION_ACCEPT, other_column=3),
            # Commit has been created after operation
            Record(id=4, modified_at=b'2021-11-30T00:10:00', commits_created_at=[b'2021-11-30T00:11:00'], operation=_OPERATION_ACCEPT, other_column=4),
        ])
    )

    assert sorted([
        Record(modified_at=b'2021-11-30T00:10:00', operation=_OPERATION_ACCEPT,                   other_column=1),
        Record(modified_at=b'2021-11-30T00:10:00', operation=_VIRTUAL_OPERATION_ACCEPT_WITH_EDIT, other_column=2),
        Record(modified_at=b'2021-11-30T00:10:00', operation=_VIRTUAL_OPERATION_ACCEPT_WITH_EDIT, other_column=3),
        Record(modified_at=b'2021-11-30T00:10:00', operation=_OPERATION_ACCEPT,                   other_column=4),
    ]) == sorted(result)


def test_should_get_paid_operations_at_date_only():
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2020-01-25',
        feedback=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', commit_ids=to_yson_type([]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 1, b'modifiedAt': b'2020-01-24 23:59:59+03:00'},
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 2, b'modifiedAt': b'2020-01-25 00:00:00+03:00'},
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 3, b'modifiedAt': b'2020-01-25 23:59:59+03:00'},
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 4, b'modifiedAt': b'2020-01-26 00:00:00+03:00'},
                   ]))
        ]),
        social_commit_event=nile_ut.Table([])
    )

    assert sorted([
        Record(position=b'pos', source=b'src', type=b'type', operation=_OPERATION_NEED_INFO, modified_by=2, modified_at=b'2020-01-25T00:00:00+03:00'),
        Record(position=b'pos', source=b'src', type=b'type', operation=_OPERATION_NEED_INFO, modified_by=3, modified_at=b'2020-01-25T23:59:59+03:00'),
    ]) == sorted(result)


@pytest.mark.parametrize('operation', PAID_OPERATIONS - {_OPERATION_NEED_INFO})
def test_should_not_get_paid_operations_at_date_if_earlier_already_paid_non_need_info_operations_exist(operation):
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2020-01-25',
        feedback=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', commit_ids=to_yson_type([]),
                   history=to_yson_type([
                       {b'operation': operation, b'modifiedBy': 1, b'modifiedAt': b'2020-01-24 00:00:00+03:00'},
                       {b'operation': operation, b'modifiedBy': 2, b'modifiedAt': b'2020-01-25 00:00:00+03:00'},
                   ]))
        ]),
        social_commit_event=nile_ut.Table([])
    )

    assert [] == result


def test_should_ignore_timezone_when_getting_paid_operations_at_date():
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2020-01-25',
        feedback=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', commit_ids=to_yson_type([]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 1, b'modifiedAt': b'2020-01-24 23:59:59+04:00'},
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 2, b'modifiedAt': b'2020-01-25 00:00:00-01:00'},
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 3, b'modifiedAt': b'2020-01-25 23:59:59+04:00'},
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 4, b'modifiedAt': b'2020-01-26 00:00:00-05:00'},
                   ]))
        ]),
        social_commit_event=nile_ut.Table([])
    )

    assert sorted([
        Record(position=b'pos', source=b'src', type=b'type', operation=_OPERATION_NEED_INFO, modified_by=2, modified_at=b'2020-01-25T00:00:00-01:00'),
        Record(position=b'pos', source=b'src', type=b'type', operation=_OPERATION_NEED_INFO, modified_by=3, modified_at=b'2020-01-25T23:59:59+04:00'),
    ]) == sorted(result)


def test_should_get_only_paid_operations_at_date():
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2021-11-30',
        feedback=nile_ut.Table([
            Record(id=1, position=b'position 1', source=b'source 1', type=b'type 1', commit_ids=to_yson_type([]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_NEED_INFO,  b'modifiedBy': 1, b'modifiedAt': b'2021-11-30 01:01:01+03:00'},
                       {b'operation': b'non-paid operation', b'modifiedBy': 1, b'modifiedAt': b'2021-11-30 01:01:02+03:00'},
                       {b'operation': _OPERATION_ACCEPT,     b'modifiedBy': 1, b'modifiedAt': b'2021-11-30 01:01:03+03:00'},
                   ])),
            Record(id=2, position=b'position 2', source=b'source 2', type=b'type 2', commit_ids=to_yson_type([]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_ACCEPT,  b'modifiedBy': 2, b'modifiedAt': b'2021-11-30 02:02:01+03:00'},
                   ]))
        ]),
        social_commit_event=nile_ut.Table([])
    )

    assert sorted([
        Record(position=b'position 1', source=b'source 1', type=b'type 1', operation=_OPERATION_NEED_INFO, modified_by=1, modified_at=b'2021-11-30T01:01:01+03:00'),
        Record(position=b'position 1', source=b'source 1', type=b'type 1', operation=_OPERATION_ACCEPT,    modified_by=1, modified_at=b'2021-11-30T01:01:03+03:00'),
        Record(position=b'position 2', source=b'source 2', type=b'type 2', operation=_OPERATION_ACCEPT,    modified_by=2, modified_at=b'2021-11-30T02:02:01+03:00'),
    ]) == sorted(result)


def test_should_get_accept_with_edit_at_date():
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2021-11-30',
        feedback=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', commit_ids=to_yson_type([1]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_ACCEPT, b'modifiedBy': 1, b'modifiedAt': b'2021-11-30 01:01:01+03:00'}
                   ]))
        ]),
        social_commit_event=nile_ut.Table([
            Record(created_at=b'2021-11-30 01:01:01+03:00', commit_id=1, type=b'edit')
        ])
    )

    assert sorted([
        Record(position=b'pos', source=b'src', type=b'type', operation=_VIRTUAL_OPERATION_ACCEPT_WITH_EDIT, modified_by=1, modified_at=b'2021-11-30T01:01:01+03:00')
    ]) == sorted(result)


def test_should_not_get_accept_with_edit_for_commit_belongs_to_other_opertaion():
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2021-11-30',
        feedback=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'source', type=b'type', commit_ids=to_yson_type([1]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 11, b'modifiedAt': b'2021-11-30 01:01:01+03:00'},
                       {b'operation': _OPERATION_ACCEPT,    b'modifiedBy': 12, b'modifiedAt': b'2021-11-30 02:02:02+03:00'},
                   ]))
        ]),
        social_commit_event=nile_ut.Table([
            Record(created_at=b'2021-11-30 01:01:01+03:00', commit_id=1, type=b'edit'),
        ])
    )

    assert sorted([
        Record(position=b'pos', source=b'source', type=b'type', operation=_OPERATION_NEED_INFO, modified_by=11, modified_at=b'2021-11-30T01:01:01+03:00'),
        Record(position=b'pos', source=b'source', type=b'type', operation=_OPERATION_ACCEPT,    modified_by=12, modified_at=b'2021-11-30T02:02:02+03:00'),
    ]) == sorted(result)


def test_should_get_only_one_paid_operation_for_entry_with_several_commits():
    result = nile_ut.yt_run(
        get_paid_operations_at_date,
        '2021-11-30',
        feedback=nile_ut.Table([
            Record(id=1, position=b'pos', source=b'src', type=b'type', commit_ids=to_yson_type([1, 2]),
                   history=to_yson_type([
                       {b'operation': _OPERATION_NEED_INFO, b'modifiedBy': 1, b'modifiedAt': b'2021-11-30 01:01:01+03:00'}
                   ]))
        ]),
        social_commit_event=nile_ut.Table([
            Record(created_at=b'2021-11-30 01:01:01+03:00', commit_id=1, type=b'edit'),
            Record(created_at=b'2021-11-30 01:01:01+03:00', commit_id=2, type=b'edit'),
        ])
    )

    assert sorted([
        Record(position=b'pos', source=b'src', type=b'type', operation=_OPERATION_NEED_INFO, modified_by=1, modified_at=b'2021-11-30T01:01:01+03:00')
    ]) == sorted(result)


@pytest.mark.parametrize('operation', PAID_OPERATIONS)
def test_should_get_task_id_for_paid_operations(operation):
    assert get_task_id(b'source 1', b'type 1', operation) == b'feedback/' + operation + b'/source 1/type 1'


def test_should_not_get_task_id_for_non_paid_operations():
    with pytest.raises(AssertionError):
        get_task_id(b'source', b'type', b'non-paid operation')
