"""Tests for duty_teller.services.group_duty_pin_service."""

from datetime import datetime
from types import SimpleNamespace
from unittest.mock import patch

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from duty_teller.db.models import Base, Duty, User
from duty_teller.services import group_duty_pin_service as svc


@pytest.fixture
def session():
    """In-memory SQLite session with all models."""
    engine = create_engine(
        "sqlite:///:memory:", connect_args={"check_same_thread": False}
    )
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine, autocommit=False, autoflush=False)
    s = Session()
    try:
        yield s
    finally:
        s.close()
        engine.dispose()


@pytest.fixture
def user(session):
    """Create a user in DB."""
    u = User(
        telegram_user_id=123,
        full_name="Test User",
        username="testuser",
        first_name="Test",
        last_name="User",
        phone="+79001234567",
    )
    session.add(u)
    session.commit()
    session.refresh(u)
    return u


@pytest.fixture
def duty(session, user):
    """Create a duty in DB (event_type='duty')."""
    d = Duty(
        user_id=user.id,
        start_at="2025-02-20T06:00:00Z",
        end_at="2025-02-21T06:00:00Z",
        event_type="duty",
    )
    session.add(d)
    session.commit()
    session.refresh(d)
    return d


class TestFormatDutyMessage:
    """Tests for format_duty_message."""

    def test_none_duty_returns_no_duty(self):
        user = SimpleNamespace(full_name="U")
        with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
            mock_t.return_value = "No duty"
            result = svc.format_duty_message(None, user, "Europe/Moscow", "en")
        assert result == "No duty"
        mock_t.assert_called_with("en", "duty.no_duty")

    def test_none_user_returns_no_duty(self):
        duty = SimpleNamespace(
            start_at="2025-01-15T09:00:00Z", end_at="2025-01-15T18:00:00Z"
        )
        with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
            mock_t.return_value = "No duty"
            result = svc.format_duty_message(duty, None, "Europe/Moscow", "en")
        assert result == "No duty"

    def test_with_duty_and_user_returns_formatted(self):
        """Formatted message includes time range and full name only; no contact info (phone/username)."""
        duty = SimpleNamespace(
            start_at="2025-01-15T09:00:00Z",
            end_at="2025-01-15T18:00:00Z",
        )
        user = SimpleNamespace(
            full_name="Иван Иванов",
            phone="+79001234567",
            username="ivan",
        )
        with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
            mock_t.side_effect = lambda lang, key: "Duty" if key == "duty.label" else ""
            result = svc.format_duty_message(duty, user, "Europe/Moscow", "ru")
        assert "Иван Иванов" in result
        assert "Duty" in result
        # Contact info is restricted to Mini App; not shown in pinned group message.
        assert "+79001234567" not in result and "79001234567" not in result
        assert "@ivan" not in result


class TestGetDutyMessageText:
    """Tests for get_duty_message_text."""

    def test_no_current_duty_returns_no_duty(self, session):
        svc.duty_pin_cache.invalidate_pattern(("duty_message_text",))
        with patch(
            "duty_teller.services.group_duty_pin_service.get_current_duty",
            return_value=None,
        ):
            with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
                mock_t.return_value = "No duty"
                result = svc.get_duty_message_text(session, "Europe/Moscow", "en")
        assert result == "No duty"

    def test_with_current_duty_returns_formatted(self, session, duty, user):
        svc.duty_pin_cache.invalidate_pattern(("duty_message_text",))
        with patch(
            "duty_teller.services.group_duty_pin_service.get_current_duty",
            return_value=(duty, user),
        ):
            with patch("duty_teller.services.group_duty_pin_service.t") as mock_t:
                mock_t.side_effect = lambda lang, key: (
                    "Duty" if key == "duty.label" else "No duty"
                )
                result = svc.get_duty_message_text(session, "Europe/Moscow", "en")
        assert "Test User" in result
        assert "Duty" in result


class TestGetNextShiftEndUtc:
    """Tests for get_next_shift_end_utc."""

    def test_no_next_shift_returns_none(self, session):
        svc.duty_pin_cache.invalidate(("next_shift_end",))
        with patch(
            "duty_teller.services.group_duty_pin_service.get_next_shift_end",
            return_value=None,
        ):
            result = svc.get_next_shift_end_utc(session)
        assert result is None

    def test_has_next_shift_returns_naive_utc(self, session):
        svc.duty_pin_cache.invalidate(("next_shift_end",))
        naive = datetime(2025, 2, 21, 6, 0, 0)
        with patch(
            "duty_teller.services.group_duty_pin_service.get_next_shift_end",
            return_value=naive,
        ):
            result = svc.get_next_shift_end_utc(session)
        assert result == naive


class TestSavePin:
    """Tests for save_pin."""

    def test_save_pin_creates_record(self, session):
        svc.save_pin(session, chat_id=100, message_id=42)
        mid = svc.get_message_id(session, 100)
        assert mid == 42

    def test_save_pin_updates_existing(self, session):
        svc.save_pin(session, chat_id=100, message_id=42)
        svc.save_pin(session, chat_id=100, message_id=99)
        mid = svc.get_message_id(session, 100)
        assert mid == 99


class TestDeletePin:
    """Tests for delete_pin."""

    def test_delete_pin_removes_record(self, session):
        svc.save_pin(session, chat_id=200, message_id=1)
        assert svc.get_message_id(session, 200) == 1
        svc.delete_pin(session, chat_id=200)
        assert svc.get_message_id(session, 200) is None


class TestGetMessageId:
    """Tests for get_message_id."""

    def test_no_pin_returns_none(self, session):
        assert svc.get_message_id(session, 999) is None

    def test_after_save_returns_message_id(self, session):
        svc.save_pin(session, chat_id=300, message_id=77)
        assert svc.get_message_id(session, 300) == 77


class TestGetAllPinChatIds:
    """Tests for get_all_pin_chat_ids."""

    def test_empty_returns_empty_list(self, session):
        assert svc.get_all_pin_chat_ids(session) == []

    def test_returns_all_chat_ids_with_pins(self, session):
        svc.save_pin(session, chat_id=10, message_id=1)
        svc.save_pin(session, chat_id=20, message_id=2)
        chat_ids = svc.get_all_pin_chat_ids(session)
        assert set(chat_ids) == {10, 20}
