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

"""Tests for persistent activity tracking (analytics KPIs, call log, monthly spend)."""

import tempfile
from datetime import datetime, timedelta
from pathlib import Path

import pytest


@pytest.fixture
def analytics_store():
    """Create a fresh analytics store with test data."""
    from familiar.core.analytics import AnalyticsStore, EventType, UsageEvent

    with tempfile.TemporaryDirectory() as tmpdir:
        store = AnalyticsStore(db_path=Path(tmpdir) / "test_analytics.db")

        # Insert some test events
        now = datetime.now()

        # Interactive call today
        store.record(
            UsageEvent(
                user_id="user1",
                channel="dashboard",
                event_type=EventType.MESSAGE,
                tokens_in=500,
                tokens_out=200,
                cost_usd=0.005,
                latency_ms=1200,
                model="gemini-2.5-flash",
                skill="chat",
            )
        )

        # Proactive call today (scheduler)
        store.record(
            UsageEvent(
                user_id="scheduler",
                channel="internal",
                event_type=EventType.MESSAGE,
                tokens_in=1000,
                tokens_out=400,
                cost_usd=0.012,
                latency_ms=2500,
                model="gemini-2.5-flash",
                skill="briefing",
            )
        )

        # Interactive call today
        store.record(
            UsageEvent(
                user_id="user1",
                channel="dashboard",
                event_type=EventType.MESSAGE,
                tokens_in=300,
                tokens_out=150,
                cost_usd=0.003,
                latency_ms=800,
                model="gemini-2.5-flash",
            )
        )

        # Tool call (not a message — should not appear in call log)
        store.record(
            UsageEvent(
                user_id="user1",
                channel="dashboard",
                event_type=EventType.TOOL_CALL,
                skill="install_package",
                latency_ms=5000,
            )
        )

        yield store


class TestPeriodKPIs:
    def test_returns_today_month_alltime(self, analytics_store):
        kpis = analytics_store.get_period_kpis()
        assert "today" in kpis
        assert "month" in kpis
        assert "all_time" in kpis
        assert "active_model" in kpis

    def test_today_cost_matches_events(self, analytics_store):
        kpis = analytics_store.get_period_kpis()
        # 0.005 + 0.012 + 0.003 = 0.020
        assert abs(kpis["today"]["cost"] - 0.020) < 0.001

    def test_today_calls_are_message_only(self, analytics_store):
        """Message count should include all messages (not tool calls separately)."""
        kpis = analytics_store.get_period_kpis()
        # 3 messages + 1 tool call = 4 events, but calls counts messages
        assert kpis["today"]["calls"] == 3

    def test_token_counts(self, analytics_store):
        kpis = analytics_store.get_period_kpis()
        assert kpis["today"]["tokens_in"] == 1800  # 500 + 1000 + 300
        assert kpis["today"]["tokens_out"] == 750  # 200 + 400 + 150

    def test_active_model(self, analytics_store):
        kpis = analytics_store.get_period_kpis()
        assert kpis["active_model"] == "gemini-2.5-flash"

    def test_month_equals_today_for_same_day(self, analytics_store):
        kpis = analytics_store.get_period_kpis()
        assert kpis["month"]["cost"] == kpis["today"]["cost"]

    def test_alltime_equals_today_for_new_db(self, analytics_store):
        kpis = analytics_store.get_period_kpis()
        assert kpis["all_time"]["cost"] == kpis["today"]["cost"]


class TestCallLog:
    def test_returns_message_events_only(self, analytics_store):
        calls, total = analytics_store.get_call_log()
        assert total == 3  # 3 messages, not 4 (tool call excluded)
        assert len(calls) == 3

    def test_most_recent_first(self, analytics_store):
        calls, _ = analytics_store.get_call_log()
        timestamps = [c["timestamp"] for c in calls]
        assert timestamps == sorted(timestamps, reverse=True)

    def test_trigger_classification(self, analytics_store):
        calls, _ = analytics_store.get_call_log()
        triggers = {c["trigger"] for c in calls}
        assert "proactive" in triggers
        assert "interactive" in triggers

    def test_filter_proactive(self, analytics_store):
        calls, total = analytics_store.get_call_log(trigger="proactive")
        assert total == 1
        assert all(c["trigger"] == "proactive" for c in calls)

    def test_filter_interactive(self, analytics_store):
        calls, total = analytics_store.get_call_log(trigger="interactive")
        assert total == 2
        assert all(c["trigger"] == "interactive" for c in calls)

    def test_pagination(self, analytics_store):
        calls, total = analytics_store.get_call_log(page=1, per_page=2)
        assert len(calls) == 2
        assert total == 3

        calls2, _ = analytics_store.get_call_log(page=2, per_page=2)
        assert len(calls2) == 1

    def test_call_fields(self, analytics_store):
        calls, _ = analytics_store.get_call_log()
        c = calls[0]
        assert "timestamp" in c
        assert "trigger" in c
        assert "model" in c
        assert "tokens_in" in c
        assert "tokens_out" in c
        assert "cost_usd" in c
        assert "latency_ms" in c
        assert "success" in c
        assert "source" in c


class TestMonthlySpend:
    def test_total_matches_events(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        assert abs(spend["total"] - 0.020) < 0.001

    def test_proactive_vs_interactive_split(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        assert abs(spend["proactive_cost"] - 0.012) < 0.001
        assert abs(spend["interactive_cost"] - 0.008) < 0.001

    def test_projected_spend(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        assert spend["projected"] > 0
        assert spend["days_in_month"] in (28, 29, 30, 31)
        assert spend["days_elapsed"] > 0

    def test_daily_average(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        expected_avg = spend["total"] / spend["days_elapsed"]
        assert abs(spend["daily_avg"] - expected_avg) < 0.0001

    def test_last_month_zero_for_new_db(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        assert spend["last_month_total"] == 0.0

    def test_change_pct_zero_when_no_last_month(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        assert spend["change_pct"] == 0.0

    def test_all_fields_present(self, analytics_store):
        spend = analytics_store.get_monthly_spend()
        expected_keys = {
            "total",
            "daily_avg",
            "projected",
            "proactive_cost",
            "interactive_cost",
            "last_month_total",
            "change_pct",
            "days_elapsed",
            "days_in_month",
        }
        assert set(spend.keys()) == expected_keys
