# 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

"""Tests for Nonprofit Director email pipeline UI — API endpoints + workspace wiring."""

import json
from unittest import mock

import pytest

# ── Fixtures ──────────────────────────────────────────────────────────


@pytest.fixture
def pending_events_dir(tmp_path):
    """Create a temp dir and patch pending events store path."""
    store_path = tmp_path / "pending_events.json"
    with mock.patch("familiar.skills.email.pending_events._store_path", return_value=store_path):
        yield tmp_path, store_path


@pytest.fixture
def action_queue_dir(tmp_path):
    """Create a temp dir and patch action queue store path."""
    store_path = tmp_path / "email_actions.json"
    with mock.patch("familiar.skills.email.action_queue._store_path", return_value=store_path):
        yield tmp_path, store_path


def _seed_pending_events(store_path, events=None):
    """Write seed data to pending events store."""
    if events is None:
        events = [
            {
                "id": "pe_test001",
                "email_id": "msg_001",
                "email_subject": "Board Meeting Next Week",
                "email_from": "chair@example.org",
                "title": "Board Meeting",
                "start": "2026-03-20T14:00:00",
                "duration": 120,
                "location": "Office",
                "has_rsvp": True,
                "conflict": None,
                "status": "pending",
                "detected_at": "2026-03-16T10:00:00+00:00",
                "reviewed_at": None,
                "source_category": "Board",
            },
            {
                "id": "pe_test002",
                "email_id": "msg_002",
                "email_subject": "Gala Planning",
                "email_from": "events@example.org",
                "title": "Gala Planning Call",
                "start": "2026-03-18T10:00:00",
                "duration": 60,
                "location": "Zoom",
                "has_rsvp": False,
                "conflict": None,
                "status": "pending",
                "detected_at": "2026-03-16T11:00:00+00:00",
                "reviewed_at": None,
                "source_category": "Events",
            },
        ]
    store_path.write_text(json.dumps({"events": events}, indent=2))


def _seed_action_queue(store_path, actions=None):
    """Write seed data to action queue store."""
    if actions is None:
        actions = [
            {
                "id": "ea_test001",
                "email_id": "msg_010",
                "email_subject": "Thank you for your gift",
                "email_from": "donor@example.com",
                "action_type": "thank_you",
                "suggested_response": "Send thank-you letter",
                "deadline": None,
                "delegate_to": None,
                "status": "pending",
                "detected_at": "2026-03-16T09:00:00+00:00",
                "completed_at": None,
                "category": "Donor",
                "priority": "action",
            },
            {
                "id": "ea_test002",
                "email_id": "msg_011",
                "email_subject": "Grant report due March 30",
                "email_from": "funder@foundation.org",
                "action_type": "grant_report",
                "suggested_response": "",
                "deadline": "2026-03-30",
                "delegate_to": None,
                "status": "pending",
                "detected_at": "2026-03-16T09:30:00+00:00",
                "completed_at": None,
                "category": "Grants",
                "priority": "urgent",
            },
        ]
    store_path.write_text(json.dumps({"actions": actions}, indent=2))


# ── Sprint 1: API Endpoint Tests ─────────────────────────────────────


class TestPendingEventsAPI:
    """Tests for /api/nonprofit/pending-events/* endpoints."""

    def test_list_pending_events_empty(self, pending_events_dir):
        """GET returns empty list when no events exist."""
        from familiar.skills.email.pending_events import get_pending_event_store

        store = get_pending_event_store()
        events = store.get_pending()
        assert events == []
        assert store.count_pending() == 0

    def test_list_pending_events_with_data(self, pending_events_dir):
        """GET returns pending events sorted by start date."""
        _, store_path = pending_events_dir
        _seed_pending_events(store_path)
        from familiar.skills.email.pending_events import get_pending_event_store

        store = get_pending_event_store()
        events = store.get_pending()
        assert len(events) == 2
        # Should be sorted by start date — Gala (18th) before Board (20th)
        assert events[0]["title"] == "Gala Planning Call"
        assert events[1]["title"] == "Board Meeting"

    def test_confirm_event(self, pending_events_dir):
        """POST confirm marks event as confirmed."""
        _, store_path = pending_events_dir
        _seed_pending_events(store_path)
        from familiar.skills.email.pending_events import get_pending_event_store

        store = get_pending_event_store()
        result = store.mark_confirmed("pe_test001")
        assert result is True
        # Now only 1 pending
        assert store.count_pending() == 1
        # Confirmed event has reviewed_at
        event = store.get_event("pe_test001")
        assert event["status"] == "confirmed"
        assert event["reviewed_at"] is not None

    def test_dismiss_event(self, pending_events_dir):
        """POST dismiss marks event as dismissed."""
        _, store_path = pending_events_dir
        _seed_pending_events(store_path)
        from familiar.skills.email.pending_events import get_pending_event_store

        store = get_pending_event_store()
        result = store.mark_dismissed("pe_test002")
        assert result is True
        assert store.count_pending() == 1

    def test_confirm_nonexistent_event(self, pending_events_dir):
        """Confirm returns False for unknown event ID."""
        from familiar.skills.email.pending_events import get_pending_event_store

        store = get_pending_event_store()
        assert store.mark_confirmed("pe_nonexistent") is False

    def test_dismiss_nonexistent_event(self, pending_events_dir):
        """Dismiss returns False for unknown event ID."""
        from familiar.skills.email.pending_events import get_pending_event_store

        store = get_pending_event_store()
        assert store.mark_dismissed("pe_nonexistent") is False


class TestActionQueueAPI:
    """Tests for /api/nonprofit/action-queue/* endpoints."""

    def test_list_actions_empty(self, action_queue_dir):
        """GET returns empty list when no actions exist."""
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        assert store.get_pending() == []
        assert store.count_pending() == 0
        assert store.summary_by_type() == {}

    def test_list_actions_with_data(self, action_queue_dir):
        """GET returns actions sorted by priority."""
        _, store_path = action_queue_dir
        _seed_action_queue(store_path)
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        actions = store.get_pending()
        assert len(actions) == 2
        # Urgent before action
        assert actions[0]["priority"] == "urgent"
        assert actions[1]["priority"] == "action"

    def test_complete_action(self, action_queue_dir):
        """POST complete marks action as completed."""
        _, store_path = action_queue_dir
        _seed_action_queue(store_path)
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        result = store.mark_complete("ea_test001")
        assert result is True
        assert store.count_pending() == 1

    def test_delegate_action(self, action_queue_dir):
        """POST delegate marks action as delegated with delegate_to."""
        _, store_path = action_queue_dir
        _seed_action_queue(store_path)
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        result = store.mark_delegated("ea_test002", "Sarah")
        assert result is True
        assert store.count_pending() == 1

    def test_complete_nonexistent_action(self, action_queue_dir):
        """Complete returns False for unknown action ID."""
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        assert store.mark_complete("ea_nonexistent") is False

    def test_delegate_nonexistent_action(self, action_queue_dir):
        """Delegate returns False for unknown action ID."""
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        assert store.mark_delegated("ea_nonexistent", "Bob") is False

    def test_summary_by_type(self, action_queue_dir):
        """Summary groups pending actions by type."""
        _, store_path = action_queue_dir
        _seed_action_queue(store_path)
        from familiar.skills.email.action_queue import get_action_store

        store = get_action_store()
        summary = store.summary_by_type()
        assert summary == {"thank_you": 1, "grant_report": 1}


# ── Sprint 2: Workspace Wiring Tests ─────────────────────────────────


class TestWorkspaceDataSources:
    """Tests that workspace data sources are registered and return expected shapes."""

    def test_pending_events_source_registered(self):
        """pending_events source is in _WORKSPACE_SOURCES."""
        from familiar.dashboard.workspace import _WORKSPACE_SOURCES

        assert "pending_events" in _WORKSPACE_SOURCES

    def test_action_queue_source_registered(self):
        """action_queue source is in _WORKSPACE_SOURCES."""
        from familiar.dashboard.workspace import _WORKSPACE_SOURCES

        assert "action_queue" in _WORKSPACE_SOURCES

    def test_pending_events_loader_shape(self, pending_events_dir):
        """Pending events loader returns dict with events list and count metric."""
        from familiar.dashboard.workspace import _load_pending_events

        result = _load_pending_events()
        assert "events" in result
        assert "pending_events.count" in result
        assert isinstance(result["events"], list)
        assert isinstance(result["pending_events.count"], int)

    def test_action_queue_loader_shape(self, action_queue_dir):
        """Action queue loader returns dict with actions list, count, and by_type."""
        from familiar.dashboard.workspace import _load_action_queue

        result = _load_action_queue()
        assert "actions" in result
        assert "action_queue.count" in result
        assert "action_queue.by_type" in result
        assert isinstance(result["actions"], list)

    def test_pending_events_loader_with_data(self, pending_events_dir):
        """Loader returns correct count when events exist."""
        _, store_path = pending_events_dir
        _seed_pending_events(store_path)
        from familiar.dashboard.workspace import _load_pending_events

        result = _load_pending_events()
        assert result["pending_events.count"] == 2
        assert len(result["events"]) == 2

    def test_action_queue_loader_with_data(self, action_queue_dir):
        """Loader returns correct count and type breakdown."""
        _, store_path = action_queue_dir
        _seed_action_queue(store_path)
        from familiar.dashboard.workspace import _load_action_queue

        result = _load_action_queue()
        assert result["action_queue.count"] == 2
        assert result["action_queue.by_type"]["thank_you"] == 1
        assert result["action_queue.by_type"]["grant_report"] == 1


class TestWorkspaceConfig:
    """Tests that nonprofit workspace config includes pipeline sections."""

    def test_workspace_has_pending_events_section(self):
        """Nonprofit workspace includes pending_events section."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        sections = NONPROFIT_DIRECTOR.workspace["sections"]
        keys = [s["key"] for s in sections]
        assert "pending_events" in keys

    def test_workspace_has_action_queue_section(self):
        """Nonprofit workspace includes action_queue section."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        sections = NONPROFIT_DIRECTOR.workspace["sections"]
        keys = [s["key"] for s in sections]
        assert "action_queue" in keys

    def test_pending_events_section_config(self):
        """Pending events section has correct type, source, and columns."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        sections = NONPROFIT_DIRECTOR.workspace["sections"]
        section = next(s for s in sections if s["key"] == "pending_events")
        assert section["type"] == "table"
        assert section["source"] == "pending_events"
        assert section["source_key"] == "events"
        col_keys = [c["key"] for c in section["columns"]]
        assert "title" in col_keys
        assert "start" in col_keys
        assert "email_from" in col_keys

    def test_action_queue_section_config(self):
        """Action queue section has correct type, source, and columns."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        sections = NONPROFIT_DIRECTOR.workspace["sections"]
        section = next(s for s in sections if s["key"] == "action_queue")
        assert section["type"] == "table"
        assert section["source"] == "action_queue"
        assert section["source_key"] == "actions"
        col_keys = [c["key"] for c in section["columns"]]
        assert "email_subject" in col_keys
        assert "action_type" in col_keys
        assert "priority" in col_keys

    def test_overview_has_pipeline_cards(self):
        """Overview cards include pending events and action queue counts."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        cards = NONPROFIT_DIRECTOR.workspace["overview"]["cards"]
        card_keys = [c["key"] for c in cards]
        assert "pending_events_count" in card_keys
        assert "action_queue_count" in card_keys

    def test_quick_actions_has_review_pipeline(self):
        """Quick actions include Review Inbox Pipeline."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        actions = NONPROFIT_DIRECTOR.workspace["quick_actions"]
        action_keys = [a["key"] for a in actions]
        assert "review_pipeline" in action_keys

    def test_skill_weights_include_pipeline_tools(self):
        """Skill weights include all 6 pipeline tools."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        weights = NONPROFIT_DIRECTOR.skill_weights
        pipeline_tools = [
            "review_pending_events",
            "confirm_pending_event",
            "dismiss_pending_event",
            "review_action_queue",
            "complete_action",
            "delegate_action",
        ]
        for tool in pipeline_tools:
            assert tool in weights, f"Missing skill weight for {tool}"

    def test_pending_events_row_actions(self):
        """Pending events section has confirm and dismiss row actions."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        sections = NONPROFIT_DIRECTOR.workspace["sections"]
        section = next(s for s in sections if s["key"] == "pending_events")
        action_keys = [a["key"] for a in section["row_actions"]]
        assert "confirm" in action_keys
        assert "dismiss" in action_keys

    def test_action_queue_row_actions(self):
        """Action queue section has complete and delegate row actions."""
        from familiar.jobs.nonprofit_director import NONPROFIT_DIRECTOR

        sections = NONPROFIT_DIRECTOR.workspace["sections"]
        section = next(s for s in sections if s["key"] == "action_queue")
        action_keys = [a["key"] for a in section["row_actions"]]
        assert "complete" in action_keys
        assert "delegate" in action_keys
