# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Unit tests for multi-account calendar menu (sentinel-based account selection).
"""

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

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

from familiar.core.confirmations import CALENDAR_MENU_PREFIX
from familiar.skills.calendar.skill import (
    _calendar_extract_summary,
    _fan_out_calendar,
)

# ── _calendar_extract_summary tests ──────────────────────────────────────────


class TestCalendarExtractSummary:
    def test_no_events(self):
        assert _calendar_extract_summary("📅 No events for today") == "0 events"

    def test_no_calendars(self):
        assert _calendar_extract_summary("No calendars configured") == "0 events"

    def test_error_result(self):
        assert _calendar_extract_summary("❌ work: connection failed") == "error"

    def test_single_bullet(self):
        text = "📅 Calendar for Monday, March 03:\n• 9:00 AM - Team standup"
        assert _calendar_extract_summary(text) == "1 event"

    def test_multiple_bullets(self):
        text = (
            "📅 Calendar for Monday, March 03:\n"
            "• 9:00 AM - Team standup\n"
            "• 11:00 AM - Code review\n"
            "• 2:00 PM - Sprint planning"
        )
        assert _calendar_extract_summary(text) == "3 events"

    def test_unknown_format_fallback(self):
        assert _calendar_extract_summary("Something unexpected happened") == "checked"


# ── _fan_out_calendar tests ──────────────────────────────────────────────────


GOOGLE_ACCT = {"id": "google", "label": "Google", "type": "google"}
CALDAV_ACCT = {"id": "caldav", "label": "CalDAV Work", "type": "caldav"}


def _fake_executor(data, acct):
    return f"📅 Calendar for Monday:\n• 9:00 AM - Meeting ({acct['id']})"


def _failing_executor(data, acct):
    raise RuntimeError("connection timeout")


@pytest.fixture
def mock_session():
    """Create a mock session with session_context dict."""
    session = MagicMock()
    session.session_context = {}
    return session


@pytest.fixture
def mock_session_mgr(mock_session):
    """Create a mock session manager."""
    mgr = MagicMock()
    return mgr


class TestFanOutCalendar:
    def test_no_accounts_returns_error(self):
        """Empty accounts list returns configuration error."""
        result = _fan_out_calendar({}, [], _fake_executor, "events")
        assert "No calendar accounts configured" in result

    def test_single_account_passthrough(self):
        """Single account returns plain text, no sentinel."""
        result = _fan_out_calendar({}, [GOOGLE_ACCT], _fake_executor, "events")
        assert not result.startswith(CALENDAR_MENU_PREFIX)
        assert "Meeting (google)" in result

    def test_multi_account_returns_sentinel(self, mock_session, mock_session_mgr):
        """Multiple accounts with session returns CALENDAR_MENU_PREFIX sentinel."""
        data = {
            "_context": {
                "session": mock_session,
                "session_manager": mock_session_mgr,
            }
        }
        result = _fan_out_calendar(data, [GOOGLE_ACCT, CALDAV_ACCT], _fake_executor, "events")
        assert result.startswith(CALENDAR_MENU_PREFIX)
        cal_menu = mock_session.session_context["calendar_menu"]
        assert "token" in cal_menu
        assert cal_menu["kind"] == "events"
        assert "google" in cal_menu["results"]
        assert "caldav" in cal_menu["results"]
        assert len(cal_menu["summaries"]) == 2

    def test_multi_account_no_session_fallback(self):
        """Without session, falls back to concatenated text."""
        result = _fan_out_calendar({}, [GOOGLE_ACCT, CALDAV_ACCT], _fake_executor, "events")
        assert not result.startswith(CALENDAR_MENU_PREFIX)
        assert "📌 Google:" in result
        assert "📌 CalDAV Work:" in result

    def test_session_manager_save_called(self, mock_session, mock_session_mgr):
        """Session manager save is called when sentinel is generated."""
        data = {
            "_context": {
                "session": mock_session,
                "session_manager": mock_session_mgr,
            }
        }
        _fan_out_calendar(data, [GOOGLE_ACCT, CALDAV_ACCT], _fake_executor, "events")
        mock_session_mgr.save_session.assert_called_once_with(mock_session)

    def test_executor_error_captured(self, mock_session, mock_session_mgr):
        """Executor exceptions are captured with error summary."""
        data = {
            "_context": {
                "session": mock_session,
                "session_manager": mock_session_mgr,
            }
        }
        result = _fan_out_calendar(data, [GOOGLE_ACCT, CALDAV_ACCT], _failing_executor, "events")
        assert result.startswith(CALENDAR_MENU_PREFIX)
        cal_menu = mock_session.session_context["calendar_menu"]
        for _, _, summary in cal_menu["summaries"]:
            assert summary == "error"
        for acct_id, text in cal_menu["results"].items():
            assert text.startswith("❌")


# ── Public function routing tests ────────────────────────────────────────────


class TestGetEventsMenu:
    @patch("familiar.skills.calendar.skill._get_events_for_account")
    @patch(
        "familiar.skills.calendar.accounts.get_all_accounts",
        return_value=[GOOGLE_ACCT, CALDAV_ACCT],
    )
    @patch("familiar.skills.calendar.accounts.get_account", return_value=None)
    def test_multi_account_routes_to_fan_out(
        self, _get, _all, _impl, mock_session, mock_session_mgr
    ):
        """get_events with 2+ accounts uses _fan_out_calendar."""
        _impl.return_value = "📅 No events for today"
        from familiar.skills.calendar.skill import get_events

        data = {
            "_context": {
                "session": mock_session,
                "session_manager": mock_session_mgr,
            }
        }
        result = get_events(data)
        assert result.startswith(CALENDAR_MENU_PREFIX)


class TestCheckAvailabilityMenu:
    @patch("familiar.skills.calendar.skill._check_availability_for_account")
    @patch(
        "familiar.skills.calendar.accounts.get_all_accounts",
        return_value=[GOOGLE_ACCT, CALDAV_ACCT],
    )
    @patch("familiar.skills.calendar.accounts.get_account", return_value=None)
    def test_multi_account_routes_to_fan_out(
        self, _get, _all, _impl, mock_session, mock_session_mgr
    ):
        """check_availability with 2+ accounts uses _fan_out_calendar."""
        _impl.return_value = "✅ Time slot is available"
        from familiar.skills.calendar.skill import check_availability

        data = {
            "_context": {
                "session": mock_session,
                "session_manager": mock_session_mgr,
            }
        }
        result = check_availability(data)
        assert result.startswith(CALENDAR_MENU_PREFIX)


class TestFindFreeTimeMenu:
    @patch("familiar.skills.calendar.skill._find_free_time_for_account")
    @patch(
        "familiar.skills.calendar.accounts.get_all_accounts",
        return_value=[GOOGLE_ACCT, CALDAV_ACCT],
    )
    @patch("familiar.skills.calendar.accounts.get_account", return_value=None)
    def test_multi_account_routes_to_fan_out(
        self, _get, _all, _impl, mock_session, mock_session_mgr
    ):
        """find_free_time with 2+ accounts uses _fan_out_calendar."""
        _impl.return_value = "🕐 Available slots:\n• 10:00-11:00\n• 14:00-15:00"
        from familiar.skills.calendar.skill import find_free_time

        data = {
            "_context": {
                "session": mock_session,
                "session_manager": mock_session_mgr,
            }
        }
        result = find_free_time(data)
        assert result.startswith(CALENDAR_MENU_PREFIX)
