import io
import json
import pytest
from pathlib import Path
from mars_bot.cli import build_parser, resolve_text, cmd_send


def test_parser_requires_to():
    parser = build_parser()
    with pytest.raises(SystemExit):
        parser.parse_args(["send"])


def test_parser_send_with_text():
    parser = build_parser()
    ns = parser.parse_args(["send", "--to", "team", "--text", "hi"])
    assert ns.command == "send"
    assert ns.to == "team"
    assert ns.text == "hi"
    assert ns.text_file is None
    assert ns.dry_run is False


def test_parser_send_with_text_file(tmp_path: Path):
    f = tmp_path / "msg.md"
    f.write_text("hello")
    parser = build_parser()
    ns = parser.parse_args(["send", "--to", "team", "--text-file", str(f)])
    assert ns.text_file == f
    assert ns.text is None


def test_parser_send_dry_run():
    parser = build_parser()
    ns = parser.parse_args(["send", "--to", "team", "--text", "hi", "--dry-run"])
    assert ns.dry_run is True


def test_parser_text_and_text_file_mutually_exclusive():
    parser = build_parser()
    with pytest.raises(SystemExit):
        parser.parse_args(["send", "--to", "team", "--text", "a", "--text-file", "b"])


def test_resolve_text_from_string():
    assert resolve_text(text="hello", text_file=None, stdin=io.StringIO("")) == "hello"


def test_resolve_text_from_file(tmp_path: Path):
    f = tmp_path / "msg.md"
    f.write_text("from file")
    assert resolve_text(text=None, text_file=f, stdin=io.StringIO("")) == "from file"


def test_resolve_text_from_stdin():
    assert resolve_text(text=None, text_file=None, stdin=io.StringIO("piped")) == "piped"


def test_resolve_text_empty_raises():
    from mars_bot.cli import CLIError
    with pytest.raises(CLIError, match="empty"):
        resolve_text(text=None, text_file=None, stdin=io.StringIO("   \n\n"))


def test_resolve_text_file_not_found_raises_cli_error():
    from mars_bot.cli import CLIError
    missing = Path("/nonexistent/path/that/does/not/exist.md")
    with pytest.raises(CLIError, match="text file not found"):
        resolve_text(text=None, text_file=missing, stdin=io.StringIO(""))


def _setup_project(tmp_path: Path, chats: dict = None):
    (tmp_path / ".env").write_text("TELEGRAM_BOT_TOKEN=test-token\n")
    (tmp_path / "chats.json").write_text(json.dumps(chats or {"team": -100123, "test": 42}))
    return tmp_path


def test_cmd_send_happy_path(tmp_path: Path, mocker):
    _setup_project(tmp_path)
    mock_send = mocker.patch("mars_bot.cli.send_message", return_value={"message_id": 1})
    parser = build_parser()
    args = parser.parse_args(["send", "--to", "team", "--text", "hello"])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc == 0
    mock_send.assert_called_once()
    call_kwargs = mock_send.call_args
    assert call_kwargs.args[0] == "test-token"
    assert call_kwargs.args[1] == -100123
    # Лог записан
    log_path = tmp_path / "data" / "sent.log"
    assert log_path.exists()
    assert "team" in log_path.read_text()


def test_cmd_send_unknown_alias(tmp_path: Path, mocker):
    _setup_project(tmp_path)
    mock_send = mocker.patch("mars_bot.cli.send_message")
    parser = build_parser()
    args = parser.parse_args(["send", "--to", "nope", "--text", "x"])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc != 0
    mock_send.assert_not_called()


def test_cmd_send_dry_run_does_not_call_api(tmp_path: Path, mocker, capsys):
    _setup_project(tmp_path)
    mock_send = mocker.patch("mars_bot.cli.send_message")
    parser = build_parser()
    args = parser.parse_args(["send", "--to", "team", "--text", "**bold**", "--dry-run"])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc == 0
    mock_send.assert_not_called()
    out = capsys.readouterr().out
    assert "<b>bold</b>" in out


def test_cmd_send_rate_limit_blocks_at_full(tmp_path: Path, mocker):
    _setup_project(tmp_path)
    mock_send = mocker.patch("mars_bot.cli.send_message", return_value={"message_id": 1})
    # 20 свежих записей — лимит уже выбран, любая отправка должна быть отклонена
    from datetime import datetime, timezone
    log_path = tmp_path / "data" / "sent.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    ts = datetime.now(timezone.utc).isoformat()
    log_path.write_text("".join(f"{ts} | team | x\n" for _ in range(20)))

    parser = build_parser()
    args = parser.parse_args(["send", "--to", "team", "--text", "x"])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc == 5
    mock_send.assert_not_called()


def test_cmd_send_rate_limit_counts_chunks_not_calls(tmp_path: Path, mocker):
    """Лимит — на количество TG-сообщений, а не на вызовы CLI.
    Если длинный текст режется на 3 чанка, отправка засчитывается как 3."""
    _setup_project(tmp_path)
    mock_send = mocker.patch("mars_bot.cli.send_message", return_value={"message_id": 1})
    # 18 свежих записей в логе. Длинный текст должен дать >= 3 чанков → 18+3 > 20 → reject.
    from datetime import datetime, timezone
    log_path = tmp_path / "data" / "sent.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    ts = datetime.now(timezone.utc).isoformat()
    log_path.write_text("".join(f"{ts} | team | x\n" for _ in range(18)))

    long_text = "\n\n".join(["x" * 2000 for _ in range(4)])
    # Подтверждаем число чанков детерминированно
    from mars_bot.format import md_to_html_chunks
    expected_chunks = len(md_to_html_chunks(long_text))
    assert expected_chunks >= 3, "test setup expects >=3 chunks from this text"

    parser = build_parser()
    args = parser.parse_args(["send", "--to", "team", "--text", long_text])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc == 5
    mock_send.assert_not_called()


def test_cmd_send_splits_long_message_and_logs_per_chunk(tmp_path: Path, mocker):
    _setup_project(tmp_path)
    mock_send = mocker.patch("mars_bot.cli.send_message", return_value={"message_id": 1})
    long_text = "\n\n".join(["x" * 2000 for _ in range(4)])
    parser = build_parser()
    args = parser.parse_args(["send", "--to", "team", "--text", long_text])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc == 0

    from mars_bot.format import md_to_html_chunks
    expected = len(md_to_html_chunks(long_text))
    assert mock_send.call_count == expected

    # В лог пишем строку на каждый успешно отправленный чанк
    log_path = tmp_path / "data" / "sent.log"
    lines = [l for l in log_path.read_text().splitlines() if l.strip()]
    assert len(lines) == expected


def test_cmd_send_partial_failure_logs_successful_chunks(tmp_path: Path, mocker):
    """Если третий чанк падает, первые два должны быть залогированы, stderr показывает прогресс."""
    _setup_project(tmp_path)
    from mars_bot.telegram import TelegramError
    call_count = {"n": 0}

    def fake_send(*_args, **_kwargs):
        call_count["n"] += 1
        if call_count["n"] >= 3:
            raise TelegramError("simulated failure on chunk 3")
        return {"message_id": call_count["n"]}

    mocker.patch("mars_bot.cli.send_message", side_effect=fake_send)
    long_text = "\n\n".join(["x" * 2000 for _ in range(4)])
    parser = build_parser()
    args = parser.parse_args(["send", "--to", "team", "--text", long_text])
    rc = cmd_send(args, project_root=tmp_path, stdin=io.StringIO(""))
    assert rc == 6  # TelegramError

    log_path = tmp_path / "data" / "sent.log"
    assert log_path.exists()
    lines = [l for l in log_path.read_text().splitlines() if l.strip()]
    assert len(lines) == 2  # ровно два успешно отправленных чанка
