"""Unit tests for TelegramMessageRepository.detect_message_gaps.

All tests are pure unit tests — no Doris connection needed.
The warehouse (DorisClient) is mocked at the QueryBuilder level.
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock

import pytest

from repo_core.domains.telegram.message.model import TelegramMessage
from repo_core.domains.telegram.message.repository import (
    TelegramMessageRepository,
    _to_doris_datetime,
)


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def _make_repo() -> tuple[TelegramMessageRepository, MagicMock]:
    """Return a repo instance wired to a mock DorisClient."""
    mock_doris = MagicMock()
    mock_doris.find_sequential_gaps = AsyncMock(return_value=[])

    mock_warehouse_qb = MagicMock()
    mock_warehouse_qb.client = mock_doris

    repo = TelegramMessageRepository.__new__(TelegramMessageRepository)
    repo.model = TelegramMessage
    repo._warehouse = mock_warehouse_qb
    repo._db = None
    repo._search = None
    repo._needs_db = True
    repo._needs_search = True
    repo._needs_doris = True
    return repo, mock_doris


# ---------------------------------------------------------------------------
# _to_doris_datetime helper
# ---------------------------------------------------------------------------

class TestToDorisDatetime:
    def test_strips_timezone_offset(self):
        result = _to_doris_datetime("2026-06-22T14:50:40+00:00")
        assert result == "2026-06-22 14:50:40"

    def test_strips_microseconds_and_timezone(self):
        result = _to_doris_datetime("2026-06-22T14:50:40.123456+00:00")
        assert result == "2026-06-22 14:50:40"

    def test_plain_iso_no_timezone(self):
        result = _to_doris_datetime("2026-06-22T14:50:40")
        assert result == "2026-06-22 14:50:40"

    def test_already_mysql_format(self):
        result = _to_doris_datetime("2026-06-22 14:50:40")
        assert result == "2026-06-22 14:50:40"

    def test_invalid_string_passthrough(self):
        result = _to_doris_datetime("not-a-date")
        assert result == "not-a-date"

    def test_negative_timezone_offset(self):
        result = _to_doris_datetime("2026-06-22T10:50:40-04:00")
        # fromisoformat converts to the given offset, then strftime drops tz
        assert result == "2026-06-22 10:50:40"


# ---------------------------------------------------------------------------
# detect_message_gaps — basic behaviour
# ---------------------------------------------------------------------------

class TestDetectMessageGaps:
    async def test_returns_empty_when_no_gaps(self):
        repo, mock_doris = _make_repo()
        mock_doris.find_sequential_gaps = AsyncMock(return_value=[])

        gaps = await repo.detect_message_gaps(peer_id=12345)

        assert gaps == []
        mock_doris.find_sequential_gaps.assert_called_once()

    async def test_returns_typed_gap_dicts(self):
        repo, mock_doris = _make_repo()
        mock_doris.find_sequential_gaps = AsyncMock(return_value=[
            {"peer_id": 12345, "gap_start": 4,  "gap_end": 6},
            {"peer_id": 12345, "gap_start": 10, "gap_end": 12},
        ])

        gaps = await repo.detect_message_gaps(peer_id=12345)

        assert len(gaps) == 2
        assert gaps[0] == {"peer_id": 12345, "gap_start": 4,  "gap_end": 6}
        assert gaps[1] == {"peer_id": 12345, "gap_start": 10, "gap_end": 12}

    async def test_values_are_ints(self):
        """Rows from Doris may have Decimal/str types — cast to int."""
        repo, mock_doris = _make_repo()
        mock_doris.find_sequential_gaps = AsyncMock(return_value=[
            {"peer_id": "12345", "gap_start": "4", "gap_end": "6"},
        ])

        gaps = await repo.detect_message_gaps(peer_id=12345)

        assert isinstance(gaps[0]["peer_id"], int)
        assert isinstance(gaps[0]["gap_start"], int)
        assert isinstance(gaps[0]["gap_end"], int)

    async def test_passes_correct_table_and_columns(self):
        repo, mock_doris = _make_repo()

        await repo.detect_message_gaps(peer_id=99)

        call = mock_doris.find_sequential_gaps.call_args
        assert call.kwargs["table"] == "telegram_messages"
        assert call.kwargs["sequence_column"] == "message_id"
        assert call.kwargs["partition_column"] == "peer_id"
        assert call.kwargs["partition_value"] == 99

    async def test_passes_min_and_max_id(self):
        repo, mock_doris = _make_repo()

        await repo.detect_message_gaps(peer_id=99, min_id=100, max_id=500)

        call = mock_doris.find_sequential_gaps.call_args
        assert call.kwargs["min_sequence"] == 100
        assert call.kwargs["max_sequence"] == 500


# ---------------------------------------------------------------------------
# detect_message_gaps — datetime format normalisation
# ---------------------------------------------------------------------------

class TestDetectMessageGapsDatetimeFormat:
    async def test_timezone_aware_iso_is_normalised(self):
        """ISO 8601 with +00:00 must be converted to plain YYYY-MM-DD HH:MM:SS
        before being passed to Doris (which cannot parse timezone offsets)."""
        repo, mock_doris = _make_repo()

        await repo.detect_message_gaps(
            peer_id=99,
            min_sent_at="2026-06-22T14:50:40.123456+00:00",
        )

        call = mock_doris.find_sequential_gaps.call_args
        extra_params = call.kwargs["extra_params"]
        assert extra_params["min_sent_at"] == "2026-06-22 14:50:40"

    async def test_max_sent_at_is_normalised(self):
        repo, mock_doris = _make_repo()

        await repo.detect_message_gaps(
            peer_id=99,
            max_sent_at="2026-06-22T18:00:00+00:00",
        )

        call = mock_doris.find_sequential_gaps.call_args
        extra_params = call.kwargs["extra_params"]
        assert extra_params["max_sent_at"] == "2026-06-22 18:00:00"

    async def test_no_time_filter_when_not_provided(self):
        repo, mock_doris = _make_repo()

        await repo.detect_message_gaps(peer_id=99)

        call = mock_doris.find_sequential_gaps.call_args
        assert call.kwargs.get("extra_where") is None
        assert call.kwargs.get("extra_params") is None

    async def test_where_clauses_added_for_time_range(self):
        repo, mock_doris = _make_repo()

        await repo.detect_message_gaps(
            peer_id=99,
            min_sent_at="2026-06-22 10:00:00",
            max_sent_at="2026-06-22 18:00:00",
        )

        call = mock_doris.find_sequential_gaps.call_args
        extra_where = call.kwargs["extra_where"]
        assert "message_sent_at >= :min_sent_at" in extra_where
        assert "message_sent_at <= :max_sent_at" in extra_where
