"""Хозяин бота и сторож над полем токена.

Два разных предмета, и оба про одно: с кем бот говорит.

**Сторож (R01–R02).** Поле токена стоит на главной странице панели, а не в
карточке прогона. Это уже сделано кодом — проверка нужна затем, чтобы поле не
уехало при первой перекройке страницы. Поэтому она не читает файл разметки, а
забирает корень у живого сервера панели: ровно то, что видит владелец.

**Хозяин (R15i).** Источник один — `telegram.owner()`. Заданный вручную
идентификатор главнее самоназначения, самоназначение — первый `/start`, чужому
бот молчит, а сохранение токена хозяина не стирает. Настоящий токен здесь не
нужен: Bot API поддельный, тот же, что в `test_telegram.py`.
"""

from __future__ import annotations

import json
import sys
import urllib.request
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from chekan import dash, telegram  # noqa: E402
from chekan.store import Run  # noqa: E402
from test_telegram import (  # noqa: E402,F401 — фикстуры берутся именем
    OWNER,
    TOKEN,
    FakeTelegram,
    _runs_root,
    panel,
    repo,
    run,
    tg,
)

STRANGER = 999999


def _page(panel, path: str) -> tuple[int, str]:
    """Разметка как её получает браузер: живой сервер, а не файл с диска."""
    with urllib.request.urlopen(panel.url + path, timeout=10) as r:
        return r.status, r.read().decode("utf-8")


# --- R01–R02: сторож над полем токена ---


def test_the_root_page_of_the_panel_carries_the_token_field(run: Run, panel):
    dash.build_index(_runs_root(run))
    code, html = _page(panel, "/")
    assert code == 200
    assert 'id="tgtoken"' in html, "поле токена уехало с главной страницы панели"
    assert 'type="password"' in html, "токен вводится открытым текстом"
    assert "/api/telegram/connect" in html, "полю токена некуда сохраняться"


def test_the_owner_field_stands_next_to_the_token_field(run: Run, panel):
    dash.build_index(_runs_root(run))
    _, html = _page(panel, "/")
    form = html.split('id="tgform"', 1)[1].split("</form>", 1)[0]
    assert 'id="tgtoken"' in form and 'id="tgowner"' in form, (
        "идентификатор владельца вводится не рядом с токеном"
    )


def test_the_run_card_has_no_token_field(run: Run, panel):
    dash.build_page(run)
    code, html = _page(panel, f"/{run.slug}/dashboard.html")
    assert code == 200
    assert "tgtoken" not in html and "BotFather" not in html, (
        "поле токена расползлось в карточку прогона"
    )


def test_the_panel_api_never_gives_the_whole_token_back(tg: FakeTelegram, run: Run, panel):
    code, saved = panel.ask("/api/telegram/connect", {"token": TOKEN, "owner_id": str(OWNER)})
    assert code == 200
    code, shown = panel.ask("/api/telegram/status")
    assert code == 200
    both = json.dumps(saved, ensure_ascii=False) + json.dumps(shown, ensure_ascii=False)
    assert TOKEN not in both and TOKEN[:10] not in both
    assert shown["token_tail"] == "…" + TOKEN[-4:]


def test_the_root_page_carries_no_token_of_its_own(tg: FakeTelegram, run: Run, panel):
    telegram.connect(TOKEN)
    dash.build_index(_runs_root(run))
    _, html = _page(panel, "/")
    assert TOKEN not in html


# --- R15i.1: хозяин вводится, сохраняется, виден ---


def test_the_owner_is_saved_with_the_token_and_read_back(tg: FakeTelegram, run: Run, panel):
    code, saved = panel.ask("/api/telegram/connect", {"token": TOKEN, "owner_id": " 424242 "})
    assert code == 200
    assert saved["owner_id"] == "424242" and saved["owner_pinned"] is True
    _, shown = panel.ask("/api/telegram/status")
    assert shown["owner_id"] == "424242"
    assert telegram.owner() == "424242"


def test_a_name_instead_of_a_number_is_refused_and_nothing_is_saved(tg: FakeTelegram, panel):
    code, payload = panel.ask("/api/telegram/connect", {"token": TOKEN, "owner_id": "@cebers"})
    assert code == 400 and "число" in payload["error"]
    assert not telegram.config_path().exists()


def test_an_empty_field_means_no_owner_was_pinned(tg: FakeTelegram, panel):
    code, saved = panel.ask("/api/telegram/connect", {"token": TOKEN, "owner_id": ""})
    assert code == 200
    assert saved["owner_id"] == "" and saved["owner_pinned"] is False
    assert telegram.owner() is None


# --- R15i.3: три случая источника хозяина ---


def test_without_a_pin_the_first_start_becomes_the_owner(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN, "")
    chat, reply = telegram.handle_update(tg.say("/start"), _runs_root(run))
    assert chat == OWNER and reply and "Связь установлена" in reply
    assert telegram.owner() == str(OWNER)


def test_a_pinned_owner_ignores_a_start_from_a_stranger(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN, str(OWNER))
    _, reply = telegram.handle_update(tg.say("/start", chat=STRANGER), _runs_root(run))
    assert reply is None, "чужой /start перехватил бота"
    assert telegram.owner() == str(OWNER), "чужой /start сдвинул хозяина"
    _, mine = telegram.handle_update(tg.say("Список"), _runs_root(run))
    assert mine and "Тестовый прогон" in mine, "хозяин остался без ответа"


def test_the_pinned_owner_beats_the_one_who_named_himself(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN, "")
    telegram.handle_update(tg.say("/start", chat=STRANGER), _runs_root(run))
    assert telegram.owner() == str(STRANGER)

    telegram.connect(TOKEN, str(OWNER))
    assert telegram.owner() == str(OWNER), "самоназванный хозяин пересилил заданного"
    _, gone = telegram.handle_update(tg.say("Список", chat=STRANGER), _runs_root(run))
    assert gone is None, "прежний самозванец всё ещё командует"
    _, mine = telegram.handle_update(tg.say("Список"), _runs_root(run))
    assert mine and "Тестовый прогон" in mine


# --- R15i.2: чужому бот молчит, а не отказывает ---


def test_a_stranger_gets_silence_and_no_message_leaves(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN, str(OWNER))
    tg.say("Список", chat=STRANGER)
    tg.say("Пауза", chat=STRANGER)
    assert telegram.poll_once(_runs_root(run)) == 2
    assert tg.sent == [], "бот ответил чужому — и тем подтвердил, что жив"
    assert not run.is_paused(), "чужой распорядился прогоном"


def test_a_stranger_gets_no_run_data(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN, str(OWNER))
    chat, reply = telegram.handle_update(tg.say("Список", chat=STRANGER), _runs_root(run))
    assert chat == STRANGER and reply is None
    assert run.state.get("title") not in (reply or "")


def test_a_stranger_does_not_move_the_last_seen_mark(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN, str(OWNER))
    telegram.handle_update(tg.say("Помощь", chat=STRANGER), _runs_root(run))
    assert telegram.load().get("last_seen") is None


# --- R15i.4: сохранение токена не стирает хозяина ---


def test_saving_the_token_again_keeps_the_self_assigned_owner(tg: FakeTelegram, run: Run):
    telegram.connect(TOKEN)
    telegram.handle_update(tg.say("/start"), _runs_root(run))
    assert telegram.load()["chat_id"] == OWNER

    telegram.connect(TOKEN)
    assert telegram.owner() == str(OWNER), "пересохранение токена отвязало хозяина"


def test_saving_the_token_again_keeps_the_pinned_owner(tg: FakeTelegram, run: Run, panel):
    panel.ask("/api/telegram/connect", {"token": TOKEN, "owner_id": str(OWNER)})
    code, again = panel.ask("/api/telegram/connect", {"token": TOKEN})
    assert code == 200
    assert again["owner_id"] == str(OWNER), "второе «Подключить» стёрло хозяина"


def test_a_pinned_owner_is_notified_before_any_start(tg: FakeTelegram, run: Run):
    """Заданный вручную хозяин делает бота говорящим сразу: `/start` не нужен."""
    telegram.connect(TOKEN, str(OWNER))
    assert telegram.notify("проверка связи") is True
    assert tg.sent and tg.sent[0]["chat_id"] == str(OWNER)


def test_forgetting_the_connection_forgets_the_owner_too(tg: FakeTelegram, panel):
    panel.ask("/api/telegram/connect", {"token": TOKEN, "owner_id": str(OWNER)})
    panel.ask("/api/telegram/disconnect", {})
    assert telegram.owner() is None
