"""Статья выплаты «Рабочих рук» берётся из таблицы Оли, а не из истории человека.

Баг 27.07.2026: enter_akty ставил статью по САМОЙ ЧАСТОЙ статье человека в истории РР.
Педагогу за уроки платят дважды в месяц, поэтому «Зарплаты педагогов» всегда побеждали
по количеству — и акт за концерт/съёмку уезжал туда же. Доп-работа пачкала ЗП-лампу,
маржинальность направлений врала, а план из таблицы Оли (событийная статья) не матчился
с фактом (статья педагогов) — обязательство висело как «просрочено» после оплаты.
"""
import olya_sheet as osh


def row(kontr, summa, date, statya, naznach="", proekt="", status="Не оплачено", проект=None):
    proekt = проект if проект is not None else proekt
    return {'Контрагент': kontr, 'Сумма': summa, 'Дата платежа': date, 'Статья расхода': statya,
            'Назначение': naznach, 'Проект': proekt, 'Статус': status, 'Способ оплаты': 'Точка'}


JULY = [
    row('СЗ Кокорев Юрий Сергеевич', '10 000,00', '27.07.2026', 'B2B ивенты',
        'Оплата Кокореву (гитара) за хор в ПГ 17.07'),
    row('СЗ Кокорев Юрий Сергеевич', '15 000,00', '27.07.2026', 'Концерты / квартирники',
        'Оплата Кокореву (гитара) за ДЕПО 11.07'),
    row('СЗ Ксения Хохлова', '10 000,00', '27.07.2026', 'B2B ивенты',
        'Оплата Ксюше Хохловой за хор в ПГ 17.07'),
    row('СЗ МИХАЙЛОВА АНАСТАСИЯ АЛЕКСЕЕВНА', '20 000,00', '27.07.2026', 'Концерты / квартирники',
        'Оплата Насте (клавиши) за концерт 25.07'),
    row('Харитонов Саша', '10 000,00', '25.08.2026', 'Съемки', 'Оплата Саше Х. за кавер сейшн 08.08'),
]


def test_same_person_different_amounts_get_different_categories():
    """Кокорев в один день получает 10к за b2b-хор и 15к за внутренний концерт — статьи разные."""
    idx = osh.expense_index(JULY)
    assert osh.lookup_category(idx, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 10_000, '07.2026') == 'расходы на b2b-ивенты'
    assert osh.lookup_category(idx, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 15_000, '07.2026') == 'расходы на внутренние ивенты'


def test_reversed_name_order_matches():
    """В таблице «СЗ Ксения Хохлова» (имя первым), в акте «ХОХЛОВА КСЕНИЯ ИГОРЕВНА»."""
    idx = osh.expense_index(JULY)
    assert osh.lookup_category(idx, 'ХОХЛОВА КСЕНИЯ ИГОРЕВНА', 10_000, '07.2026') == 'расходы на b2b-ивенты'


def test_nickname_matches_by_surname():
    """«Харитонов Саша» в таблице ↔ «ХАРИТОНОВ АЛЕКСАНДР ИГОРЕВИЧ» в акте — общая фамилия."""
    idx = osh.expense_index(JULY)
    assert osh.lookup_category(idx, 'ХАРИТОНОВ АЛЕКСАНДР ИГОРЕВИЧ', 10_000, '08.2026') == 'расходы на съемки'


def test_unknown_person_returns_none():
    """Педагог за уроки в таблице Оли не значится — статью решает прежняя логика."""
    idx = osh.expense_index(JULY)
    assert osh.lookup_category(idx, 'КЛИМОВИЧ САБИНА', 65_700, '07.2026') is None


def test_wrong_amount_returns_none():
    """Сумма не совпала — не угадываем."""
    idx = osh.expense_index(JULY)
    assert osh.lookup_category(idx, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 12_345, '07.2026') is None


def test_wrong_month_returns_none():
    idx = osh.expense_index(JULY)
    assert osh.lookup_category(idx, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 10_000, '09.2026') is None


def test_ambiguous_rows_give_up_instead_of_guessing():
    """Один человек, месяц и сумма, но статьи разные — честно возвращаем None на проверку."""
    rows = [row('СЗ Ционов Сергей', '15 000,00', '27.07.2026', 'Концерты / квартирники', 'ДЕПО'),
            row('СЗ Ционов Сергей', '15 000,00', '03.07.2026', 'Съемки', 'cover session')]
    idx = osh.expense_index(rows)
    assert osh.lookup_category(idx, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 15_000, '07.2026') is None


def test_paid_rows_count_too():
    """Статус не важен: акт приходит уже после оплаты, строка будет «Оплачено»."""
    rows = [row('СЗ КОЗЛОВ ОЛЕГ ВЛАДИМИРОВИЧ', '20 000,00', '27.07.2026', 'Концерты / квартирники',
                'Оплата Олегу (кахон) за концерт 25.07', status='Оплачено')]
    idx = osh.expense_index(rows)
    assert osh.lookup_category(idx, 'КОЗЛОВ ОЛЕГ ВЛАДИМИРОВИЧ', 20_000, '07.2026') == 'расходы на внутренние ивенты'


def test_company_rows_are_not_indexed_as_people():
    """«Яндекс Такси», «ООО ВАЗЗАП», «Наличными, на площадке» — не самозанятые, в индекс не идут."""
    rows = [row('Яндекс Такси', '1 905,00', '25.07.2026', 'Концерты / квартирники', 'Доставка оборудования'),
            row('Наличными, на площадке', '35 000,00', '25.07.2026', 'Концерты / квартирники', 'аренда крыши')]
    idx = osh.expense_index(rows)
    assert osh.lookup_category(idx, 'ТАКСИ ЯНДЕКС', 1_905, '07.2026') is None


def test_patronymic_does_not_create_false_ambiguity():
    """Отчество — не различающий признак: «Сергеевич» есть у половины города.

    Раньше акт «БАРЫШНИКОВ ВЛАДИМИР СЕРГЕЕВИЧ» цеплялся за строку «СЗ КОКОРЕВ ЮРИЙ
    СЕРГЕЕВИЧ» той же суммы через общий токен «сергеевич», выходило две статьи —
    и статья молча уезжала в дефолт-педагога. Сверяем по фамилии.
    """
    rows = [row('СЗ Барышников Владимир', '15 000,00', '03.08.2026', 'Съемки', 'cover session'),
            row('СЗ КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', '15 000,00', '25.08.2026', 'B2B ивенты', 'гитарник ВДНХ')]
    idx = osh.expense_index(rows)
    assert osh.lookup_category(idx, 'БАРЫШНИКОВ ВЛАДИМИР СЕРГЕЕВИЧ', 15_000, '08.2026') == 'расходы на съемки'
    assert osh.lookup_category(idx, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 15_000, '08.2026') == 'расходы на b2b-ивенты'


def test_namesake_by_first_name_does_not_match():
    """Тёзка по имени — не тот человек: у Русманова и Булгакова общий «Александр»."""
    rows = [row('СЗ Русманов Александр', '5 000,00', '27.07.2026', 'B2B ивенты', 'МК по кринжу')]
    idx = osh.expense_index(rows)
    assert osh.lookup_category(idx, 'БУЛГАКОВ АЛЕКСАНДР НИКОЛАЕВИЧ', 5_000, '07.2026') is None
    assert osh.lookup_category(idx, 'РУСМАНОВ АЛЕКСАНДР СЕРГЕЕВИЧ', 5_000, '07.2026') == 'расходы на b2b-ивенты'


def test_ambiguity_is_reported_not_hidden():
    """Неоднозначные строки должны быть видны вызывающему, а не тихо падать на историю."""
    rows = [row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'Съемки', 'кавер сейшн'),
            row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'B2B ивенты', 'хор ВК')]
    idx = osh.expense_index(rows)
    assert osh.lookup_category(idx, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 10_000, '08.2026') is None
    assert osh.category_candidates(idx, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 10_000, '08.2026') == {
        'расходы на съемки', 'расходы на b2b-ивенты'}


def test_no_candidates_for_stranger():
    idx = osh.expense_index(JULY)
    assert osh.category_candidates(idx, 'КЛИМОВИЧ САБИНА', 65_700, '07.2026') == set()


# ── строку Оли ищем по фамилии+сумме во ВСЕХ месяцах, двойников разводим по акту ──
# Платёж бывает в другом месяце, чем работа: cover session 25.07 оплачивается 03.08.
# Привязка к месяцу акта такие строки теряла, и статья падала обратно в педагогов.

KOKOREV = [
    row('СЗ Кокорев Юрий Сергеевич', '15 000,00', '27.07.2026', 'Концерты / квартирники',
        'Оплата Кокореву (гитара) за ДЕПО 11.07', проект='концерт ДЕПО 11.07'),
    row('СЗ Кокорев Юрий Сергеевич', '15 000,00', '03.08.2026', 'Съемки',
        'Оплата Юре Кокореву за cover session Тани Регард 25.07'),
]


def test_payment_in_next_month_is_found():
    """Съёмка 25.07, платёж 03.08 — акт июльский, строка августовская. Должны найти."""
    pr = osh.payment_rows(KOKOREV)
    m, cand = osh.match_payment(pr, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 15_000, '25.07.2026')
    assert m and m['cat'] == 'расходы на съемки'
    assert m['pay_date'] == '03.08.2026' and m['month'] == '08.2026'


def test_twins_split_by_act_date():
    """Тот же человек и сумма: акт от 11.07 — это ДЕПО, а не cover session."""
    pr = osh.payment_rows(KOKOREV)
    m, _ = osh.match_payment(pr, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 15_000, '11.07.2026')
    assert m and m['cat'] == 'расходы на внутренние ивенты'
    assert m['pay_date'] == '27.07.2026'


def test_twins_split_by_service_text_when_dates_silent():
    """Даты в акте не помогли — разводим по словам из описания услуги."""
    rows = [row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'Съемки', 'Оплата Сереже за кавер сейшн'),
            row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'B2B ивенты', 'Оплата Сергею за проведение хора')]
    pr = osh.payment_rows(rows)
    m, _ = osh.match_payment(pr, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 10_000, '20.08.2026',
                             service='Проведение хора корпоратив')
    assert m and m['cat'] == 'расходы на b2b-ивенты'


def test_unresolvable_twins_report_candidates():
    """Ничем не различить — не угадываем, отдаём кандидатов на ручную пометку."""
    rows = [row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'Съемки', 'Оплата Сереже'),
            row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'B2B ивенты', 'Оплата Сергею')]
    pr = osh.payment_rows(rows)
    m, cand = osh.match_payment(pr, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 10_000, '20.08.2026')
    assert m is None and len(cand) == 2


def test_single_row_needs_no_disambiguation():
    pr = osh.payment_rows(JULY)
    m, _ = osh.match_payment(pr, 'ХОХЛОВА КСЕНИЯ ИГОРЕВНА', 10_000, '17.07.2026')
    assert m and m['cat'] == 'расходы на b2b-ивенты' and m['pay_date'] == '27.07.2026'


def test_stranger_not_matched():
    pr = osh.payment_rows(JULY)
    m, cand = osh.match_payment(pr, 'КЛИМОВИЧ САБИНА', 65_700, '16.07.2026')
    assert m is None and cand == []


def test_single_shared_word_is_not_enough_to_decide():
    """Одно общее слово — не улика: описание в акте бывает обманчивым, лучше пометить.

    В разборе 27.07 стенд подсунул Ционову чужой проект, и матчер по одному слову
    уверенно выбрал не ту статью. Порог — минимум два значимых совпадения.
    """
    rows = [row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'Съемки', 'Оплата за кавер сейшн'),
            row('СЗ Ционов Сергей', '10 000,00', '25.08.2026', 'B2B ивенты', 'Оплата за проведение хора')]
    pr = osh.payment_rows(rows)
    m, cand = osh.match_payment(pr, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 10_000, '20.08.2026', service='хора')
    assert m is None and len(cand) == 2
    m2, _ = osh.match_payment(pr, 'ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', 10_000, '20.08.2026',
                              service='проведение хора на корпоративе')
    assert m2 and m2['cat'] == 'расходы на b2b-ивенты'


def test_twins_split_by_payment_date():
    """Акт Рабочих рук выписан датой платежа: акт от 27.07 — это строка с «Датой платежа»
    27.07, а не будущий cover session, который оплатят только 03.08.

    Даты события в назначении («ДЕПО 11.07») тут молчат: 27.07 в них не встречается.
    """
    pr = osh.payment_rows(KOKOREV)
    m, _ = osh.match_payment(pr, 'КОКОРЕВ ЮРИЙ СЕРГЕЕВИЧ', 15_000, '27.07.2026')
    assert m and m['cat'] == 'расходы на внутренние ивенты'
    assert m['pay_date'] == '27.07.2026'


# ── колонка «Контрагент» → уже занесённые планки (матч без перезаписи плана) ──

def test_kontr_by_ext_maps_plan_id_to_person():
    """Планка ищется по тому же externalId, что писал enter_olya_plan: (дата|сумма|назначение)."""
    r = row('СЗ Барышников Владимир', '15 000,00', '27.07.2026', 'Концерты / квартирники',
            'Оплата Вове (бас) за ДЕПО 11.07', проект='концерт ДЕПО 11.07')
    idx = osh.kontr_by_ext([r])
    assert idx[osh.ext_id('27.07.2026', 15000.0, 'Оплата Вове (бас) за ДЕПО 11.07')] \
        == 'СЗ Барышников Владимир'


def test_kontr_by_ext_skips_companies():
    """В колонке живут и компании — их в индекс исполнителей не берём."""
    r = row('ООО ВАЗЗАП', '5 000,00', '27.07.2026', 'Сервисы', 'Подписка')
    assert osh.kontr_by_ext([r]) == {}


# ── описание выплаты: событие из таблицы Оли рядом с ФИО ──

def test_akt_description_carries_event():
    """В Финтабло должно быть видно, за что заплатили, а не только кому и когда."""
    import enter_akty as ea
    d = ea.akt_description('ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', '27.07.2026',
                           'Оплата Ционову (клавиши) за ДЕПО 11.07 концерт ДЕПО 11.07')
    assert 'Ционов Сергей Вячеславович' in d and '27.07.2026' in d and 'ДЕПО 11.07' in d


def test_akt_description_without_event_stays_as_before():
    import enter_akty as ea
    d = ea.akt_description('ЦИОНОВ СЕРГЕЙ ВЯЧЕСЛАВОВИЧ', '27.07.2026', '')
    assert d == 'Выплата Ционов Сергей Вячеславович 27.07.2026 (Рабочие руки, по акту)'
