# 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

"""
Usage Analytics Module - Phase 2

Tracks usage metrics for capacity planning and cost management:
- Per-user message counts
- Token consumption
- Cost tracking by user/skill
- Latency monitoring
- Budget alerts

Storage:
- SQLite for events (append-only, efficient)
- Aggregations cached for dashboard
- CSV export for reporting

Dashboard Integration:
- Charts via Chart.js
- Real-time updates via HTMX
- CSV download endpoint
"""

from __future__ import annotations

import csv
import io
import json
import logging
import os
import sqlite3
from contextlib import contextmanager
from dataclasses import asdict, dataclass, field
from datetime import datetime, timedelta, timezone
from enum import Enum
from pathlib import Path
from typing import Dict, List, Tuple

logger = logging.getLogger(__name__)

# Default data directory
DATA_DIR = Path(os.environ.get("FAMILIAR_DATA_DIR", Path.home() / ".familiar" / "data"))
ANALYTICS_DB = DATA_DIR / "analytics.db"


class EventType(str, Enum):
    """Types of tracked events."""

    MESSAGE = "message"  # User message processed
    TOOL_CALL = "tool_call"  # Tool/skill invocation
    ERROR = "error"  # Processing error
    AUTH = "auth"  # Authentication event
    ADMIN = "admin"  # Admin action
    SYSTEM = "system"  # System event


class AggregationPeriod(str, Enum):
    """Aggregation time periods."""

    HOUR = "hour"
    DAY = "day"
    WEEK = "week"
    MONTH = "month"


@dataclass
class UsageEvent:
    """
    A tracked usage event.

    All fields except id and timestamp are optional to support
    various event types.
    """

    id: str = None
    timestamp: datetime = None

    # Who
    user_id: str = None
    user_email: str = None
    channel: str = None  # telegram, discord, cli, etc.

    # What
    event_type: EventType = EventType.MESSAGE
    skill: str = None  # Which skill/tool was used
    action: str = None  # Specific action within skill

    # Metrics
    tokens_in: int = 0
    tokens_out: int = 0
    cost_usd: float = 0.0
    latency_ms: int = 0

    # Context
    model: str = None
    success: bool = True
    error_message: str = None
    metadata: Dict = field(default_factory=dict)

    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.now(timezone.utc)
        if self.id is None:
            import secrets

            self.id = secrets.token_urlsafe(12)

    @property
    def total_tokens(self) -> int:
        return self.tokens_in + self.tokens_out

    def to_dict(self) -> Dict:
        """Convert to dictionary for storage."""
        d = asdict(self)
        d["timestamp"] = self.timestamp.isoformat()
        d["event_type"] = (
            self.event_type.value if isinstance(self.event_type, EventType) else self.event_type
        )
        d["metadata"] = json.dumps(self.metadata) if self.metadata else "{}"
        return d

    @classmethod
    def from_dict(cls, d: Dict) -> "UsageEvent":
        """Create from dictionary."""
        d = d.copy()
        if isinstance(d.get("timestamp"), str):
            d["timestamp"] = datetime.fromisoformat(d["timestamp"])
        if isinstance(d.get("event_type"), str):
            d["event_type"] = EventType(d["event_type"])
        if isinstance(d.get("metadata"), str):
            d["metadata"] = json.loads(d["metadata"]) if d["metadata"] else {}
        return cls(**d)


@dataclass
class UsageSummary:
    """Aggregated usage summary."""

    period_start: datetime
    period_end: datetime

    # Counts
    total_events: int = 0
    total_messages: int = 0
    total_tool_calls: int = 0
    total_errors: int = 0

    # Tokens
    total_tokens_in: int = 0
    total_tokens_out: int = 0

    # Cost
    total_cost_usd: float = 0.0

    # Performance
    avg_latency_ms: float = 0.0
    p95_latency_ms: float = 0.0

    # Breakdowns
    by_user: Dict[str, Dict] = field(default_factory=dict)
    by_skill: Dict[str, Dict] = field(default_factory=dict)
    by_channel: Dict[str, Dict] = field(default_factory=dict)
    by_model: Dict[str, Dict] = field(default_factory=dict)

    @property
    def total_tokens(self) -> int:
        return self.total_tokens_in + self.total_tokens_out

    @property
    def success_rate(self) -> float:
        if self.total_events == 0:
            return 1.0
        return 1.0 - (self.total_errors / self.total_events)


@dataclass
class BudgetAlert:
    """Budget alert configuration."""

    id: str = None
    name: str = None

    # Scope
    user_id: str = None  # None = org-wide

    # Limits
    monthly_budget_usd: float = 100.0
    alert_threshold_pct: float = 80.0  # Alert at 80%

    # Notification
    notify_email: str = None
    notify_webhook: str = None

    # State
    last_alerted: datetime = None
    enabled: bool = True


# ============================================================
# ANALYTICS STORE
# ============================================================

SCHEMA = """
-- Events table (append-only)
CREATE TABLE IF NOT EXISTS events (
    id TEXT PRIMARY KEY,
    timestamp TEXT NOT NULL,
    user_id TEXT,
    user_email TEXT,
    channel TEXT,
    event_type TEXT NOT NULL,
    skill TEXT,
    action TEXT,
    tokens_in INTEGER DEFAULT 0,
    tokens_out INTEGER DEFAULT 0,
    cost_usd REAL DEFAULT 0,
    latency_ms INTEGER DEFAULT 0,
    model TEXT,
    success INTEGER DEFAULT 1,
    error_message TEXT,
    metadata TEXT
);

-- Indexes for common queries
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
CREATE INDEX IF NOT EXISTS idx_events_user ON events(user_id);
CREATE INDEX IF NOT EXISTS idx_events_type ON events(event_type);
CREATE INDEX IF NOT EXISTS idx_events_skill ON events(skill);

-- Daily aggregates (materialized for performance)
CREATE TABLE IF NOT EXISTS daily_stats (
    date TEXT NOT NULL,
    user_id TEXT,
    channel TEXT,
    skill TEXT,
    model TEXT,
    event_count INTEGER DEFAULT 0,
    message_count INTEGER DEFAULT 0,
    tool_count INTEGER DEFAULT 0,
    error_count INTEGER DEFAULT 0,
    tokens_in INTEGER DEFAULT 0,
    tokens_out INTEGER DEFAULT 0,
    cost_usd REAL DEFAULT 0,
    total_latency_ms INTEGER DEFAULT 0,
    PRIMARY KEY (date, user_id, channel, skill, model)
);

-- Budget alerts
CREATE TABLE IF NOT EXISTS budget_alerts (
    id TEXT PRIMARY KEY,
    name TEXT,
    user_id TEXT,
    monthly_budget_usd REAL DEFAULT 100,
    alert_threshold_pct REAL DEFAULT 80,
    notify_email TEXT,
    notify_webhook TEXT,
    last_alerted TEXT,
    enabled INTEGER DEFAULT 1
);
"""


class AnalyticsStore:
    """
    Storage and querying for usage analytics.

    Usage:
        store = AnalyticsStore()

        # Record event
        event = UsageEvent(
            user_id="user123",
            event_type=EventType.MESSAGE,
            tokens_in=100,
            tokens_out=200,
            cost_usd=0.01,
            latency_ms=500
        )
        store.record(event)

        # Query events
        events = store.query(
            start=datetime.now() - timedelta(days=7),
            user_id="user123"
        )

        # Get summary
        summary = store.get_summary(
            start=datetime.now() - timedelta(days=30),
            end=datetime.now()
        )
    """

    def __init__(self, db_path: Path = None):
        self.db_path = db_path or ANALYTICS_DB
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    def _init_db(self):
        """Initialize database schema."""
        with self._connect() as conn:
            conn.executescript(SCHEMA)

    @contextmanager
    def _connect(self):
        """Get database connection."""
        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        finally:
            conn.close()

    # ---- Recording ----

    def record(self, event: UsageEvent):
        """Record a usage event."""
        with self._connect() as conn:
            d = event.to_dict()
            conn.execute(
                """
                INSERT INTO events (
                    id, timestamp, user_id, user_email, channel,
                    event_type, skill, action,
                    tokens_in, tokens_out, cost_usd, latency_ms,
                    model, success, error_message, metadata
                ) VALUES (
                    :id, :timestamp, :user_id, :user_email, :channel,
                    :event_type, :skill, :action,
                    :tokens_in, :tokens_out, :cost_usd, :latency_ms,
                    :model, :success, :error_message, :metadata
                )
            """,
                d,
            )

            # Update daily stats
            self._update_daily_stats(conn, event)

    def record_message(
        self,
        user_id: str,
        channel: str,
        tokens_in: int,
        tokens_out: int,
        cost_usd: float,
        latency_ms: int,
        model: str = None,
        skill: str = None,
        user_email: str = None,
    ):
        """Convenience method to record a message event."""
        event = UsageEvent(
            user_id=user_id,
            user_email=user_email,
            channel=channel,
            event_type=EventType.MESSAGE,
            skill=skill,
            tokens_in=tokens_in,
            tokens_out=tokens_out,
            cost_usd=cost_usd,
            latency_ms=latency_ms,
            model=model,
        )
        self.record(event)

    def record_tool_call(
        self,
        user_id: str,
        channel: str,
        skill: str,
        action: str = None,
        latency_ms: int = 0,
        success: bool = True,
        error_message: str = None,
        metadata: dict = None,
    ):
        """Record a tool/skill invocation."""
        event = UsageEvent(
            user_id=user_id,
            channel=channel,
            event_type=EventType.TOOL_CALL,
            skill=skill,
            action=action,
            latency_ms=latency_ms,
            success=success,
            error_message=error_message,
            metadata=metadata or {},
        )
        self.record(event)

    def record_error(
        self, user_id: str = None, channel: str = None, error_message: str = None, skill: str = None
    ):
        """Record an error event."""
        event = UsageEvent(
            user_id=user_id,
            channel=channel,
            event_type=EventType.ERROR,
            skill=skill,
            success=False,
            error_message=error_message,
        )
        self.record(event)

    def _update_daily_stats(self, conn, event: UsageEvent):
        """Update daily aggregates."""
        date_str = event.timestamp.strftime("%Y-%m-%d")

        # Determine counts
        is_message = 1 if event.event_type == EventType.MESSAGE else 0
        is_tool = 1 if event.event_type == EventType.TOOL_CALL else 0
        is_error = 1 if event.event_type == EventType.ERROR or not event.success else 0

        conn.execute(
            """
            INSERT INTO daily_stats (
                date, user_id, channel, skill, model,
                event_count, message_count, tool_count, error_count,
                tokens_in, tokens_out, cost_usd, total_latency_ms
            ) VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT (date, user_id, channel, skill, model) DO UPDATE SET
                event_count = event_count + 1,
                message_count = message_count + ?,
                tool_count = tool_count + ?,
                error_count = error_count + ?,
                tokens_in = tokens_in + ?,
                tokens_out = tokens_out + ?,
                cost_usd = cost_usd + ?,
                total_latency_ms = total_latency_ms + ?
        """,
            (
                date_str,
                event.user_id or "",
                event.channel or "",
                event.skill or "",
                event.model or "",
                is_message,
                is_tool,
                is_error,
                event.tokens_in,
                event.tokens_out,
                event.cost_usd,
                event.latency_ms,
                # Update values
                is_message,
                is_tool,
                is_error,
                event.tokens_in,
                event.tokens_out,
                event.cost_usd,
                event.latency_ms,
            ),
        )

    # ---- Querying ----

    def query(
        self,
        start: datetime = None,
        end: datetime = None,
        user_id: str = None,
        channel: str = None,
        event_type: EventType = None,
        skill: str = None,
        limit: int = 1000,
        offset: int = 0,
    ) -> List[UsageEvent]:
        """
        Query events with filters.

        Args:
            start: Start timestamp (inclusive)
            end: End timestamp (exclusive)
            user_id: Filter by user
            channel: Filter by channel
            event_type: Filter by event type
            skill: Filter by skill
            limit: Max results
            offset: Pagination offset

        Returns:
            List of UsageEvent objects
        """
        conditions = []
        params = []

        if start:
            conditions.append("timestamp >= ?")
            params.append(start.isoformat())
        if end:
            conditions.append("timestamp < ?")
            params.append(end.isoformat())
        if user_id:
            conditions.append("user_id = ?")
            params.append(user_id)
        if channel:
            conditions.append("channel = ?")
            params.append(channel)
        if event_type:
            conditions.append("event_type = ?")
            params.append(event_type.value if isinstance(event_type, EventType) else event_type)
        if skill:
            conditions.append("skill = ?")
            params.append(skill)

        where = " AND ".join(conditions) if conditions else "1=1"

        with self._connect() as conn:
            rows = conn.execute(
                f"""
                SELECT * FROM events
                WHERE {where}
                ORDER BY timestamp DESC
                LIMIT ? OFFSET ?
            """,
                params + [limit, offset],
            ).fetchall()

        return [UsageEvent.from_dict(dict(row)) for row in rows]

    def get_summary(
        self, start: datetime = None, end: datetime = None, user_id: str = None, channel: str = None
    ) -> UsageSummary:
        """
        Get aggregated usage summary.

        Args:
            start: Period start
            end: Period end
            user_id: Filter by user (None = all users)
            channel: Filter by channel

        Returns:
            UsageSummary with totals and breakdowns
        """
        if start is None:
            start = datetime.now(timezone.utc) - timedelta(days=30)
        if end is None:
            end = datetime.now(timezone.utc)

        conditions = ["date >= ? AND date <= ?"]
        params = [start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d")]

        if user_id:
            conditions.append("user_id = ?")
            params.append(user_id)
        if channel:
            conditions.append("channel = ?")
            params.append(channel)

        where = " AND ".join(conditions)

        with self._connect() as conn:
            # Overall totals
            row = conn.execute(
                f"""
                SELECT
                    SUM(event_count) as total_events,
                    SUM(message_count) as total_messages,
                    SUM(tool_count) as total_tool_calls,
                    SUM(error_count) as total_errors,
                    SUM(tokens_in) as total_tokens_in,
                    SUM(tokens_out) as total_tokens_out,
                    SUM(cost_usd) as total_cost_usd,
                    AVG(CASE WHEN event_count > 0 THEN total_latency_ms * 1.0 / event_count ELSE 0 END) as avg_latency_ms
                FROM daily_stats
                WHERE {where}
            """,
                params,
            ).fetchone()

            summary = UsageSummary(
                period_start=start,
                period_end=end,
                total_events=row["total_events"] or 0,
                total_messages=row["total_messages"] or 0,
                total_tool_calls=row["total_tool_calls"] or 0,
                total_errors=row["total_errors"] or 0,
                total_tokens_in=row["total_tokens_in"] or 0,
                total_tokens_out=row["total_tokens_out"] or 0,
                total_cost_usd=row["total_cost_usd"] or 0.0,
                avg_latency_ms=row["avg_latency_ms"] or 0.0,
            )

            # By user
            for row in conn.execute(
                f"""
                SELECT user_id,
                    SUM(message_count) as messages,
                    SUM(cost_usd) as cost,
                    SUM(tokens_in + tokens_out) as tokens
                FROM daily_stats
                WHERE {where} AND user_id != ''
                GROUP BY user_id
                ORDER BY cost DESC
            """,
                params,
            ):
                summary.by_user[row["user_id"]] = {
                    "messages": row["messages"],
                    "cost": row["cost"],
                    "tokens": row["tokens"],
                }

            # By skill
            for row in conn.execute(
                f"""
                SELECT skill,
                    SUM(tool_count) as calls,
                    SUM(cost_usd) as cost,
                    AVG(CASE WHEN tool_count > 0 THEN total_latency_ms * 1.0 / tool_count ELSE 0 END) as avg_latency
                FROM daily_stats
                WHERE {where} AND skill != ''
                GROUP BY skill
                ORDER BY calls DESC
            """,
                params,
            ):
                summary.by_skill[row["skill"]] = {
                    "calls": row["calls"],
                    "cost": row["cost"],
                    "avg_latency": row["avg_latency"],
                }

            # By channel
            for row in conn.execute(
                f"""
                SELECT channel,
                    SUM(message_count) as messages,
                    SUM(cost_usd) as cost
                FROM daily_stats
                WHERE {where} AND channel != ''
                GROUP BY channel
                ORDER BY messages DESC
            """,
                params,
            ):
                summary.by_channel[row["channel"]] = {
                    "messages": row["messages"],
                    "cost": row["cost"],
                }

            # By model
            for row in conn.execute(
                f"""
                SELECT model,
                    SUM(message_count) as messages,
                    SUM(cost_usd) as cost,
                    SUM(tokens_in + tokens_out) as tokens
                FROM daily_stats
                WHERE {where} AND model != ''
                GROUP BY model
                ORDER BY cost DESC
            """,
                params,
            ):
                summary.by_model[row["model"]] = {
                    "messages": row["messages"],
                    "cost": row["cost"],
                    "tokens": row["tokens"],
                }

        return summary

    def get_daily_costs(
        self, start: datetime = None, end: datetime = None, user_id: str = None
    ) -> List[Tuple[str, float]]:
        """Get daily cost totals for charting."""
        if start is None:
            start = datetime.now(timezone.utc) - timedelta(days=30)
        if end is None:
            end = datetime.now(timezone.utc)

        conditions = ["date >= ? AND date <= ?"]
        params = [start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d")]

        if user_id:
            conditions.append("user_id = ?")
            params.append(user_id)

        where = " AND ".join(conditions)

        with self._connect() as conn:
            rows = conn.execute(
                f"""
                SELECT date, SUM(cost_usd) as cost
                FROM daily_stats
                WHERE {where}
                GROUP BY date
                ORDER BY date
            """,
                params,
            ).fetchall()

        return [(row["date"], row["cost"]) for row in rows]

    def get_user_rankings(
        self, start: datetime = None, end: datetime = None, limit: int = 10
    ) -> List[Dict]:
        """Get top users by cost/usage."""
        if start is None:
            start = datetime.now(timezone.utc) - timedelta(days=30)
        if end is None:
            end = datetime.now(timezone.utc)

        with self._connect() as conn:
            rows = conn.execute(
                """
                SELECT
                    user_id,
                    SUM(message_count) as messages,
                    SUM(cost_usd) as cost,
                    SUM(tokens_in + tokens_out) as tokens
                FROM daily_stats
                WHERE date >= ? AND date <= ? AND user_id != ''
                GROUP BY user_id
                ORDER BY cost DESC
                LIMIT ?
            """,
                (start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d"), limit),
            ).fetchall()

        return [dict(row) for row in rows]

    # ---- Export ----

    def export_csv(self, start: datetime = None, end: datetime = None, user_id: str = None) -> str:
        """Export events to CSV string."""
        events = self.query(start=start, end=end, user_id=user_id, limit=100000)

        output = io.StringIO()
        writer = csv.writer(output)

        # Header
        writer.writerow(
            [
                "timestamp",
                "user_id",
                "user_email",
                "channel",
                "event_type",
                "skill",
                "action",
                "tokens_in",
                "tokens_out",
                "cost_usd",
                "latency_ms",
                "model",
                "success",
                "error_message",
            ]
        )

        # Rows
        for e in events:
            writer.writerow(
                [
                    e.timestamp.isoformat(),
                    e.user_id,
                    e.user_email,
                    e.channel,
                    e.event_type.value if isinstance(e.event_type, EventType) else e.event_type,
                    e.skill,
                    e.action,
                    e.tokens_in,
                    e.tokens_out,
                    e.cost_usd,
                    e.latency_ms,
                    e.model,
                    e.success,
                    e.error_message,
                ]
            )

        return output.getvalue()

    # ---- Budget Alerts ----

    def create_alert(self, alert: BudgetAlert) -> BudgetAlert:
        """Create a budget alert."""
        if alert.id is None:
            import secrets

            alert.id = secrets.token_urlsafe(8)

        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO budget_alerts (
                    id, name, user_id, monthly_budget_usd, alert_threshold_pct,
                    notify_email, notify_webhook, enabled
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """,
                (
                    alert.id,
                    alert.name,
                    alert.user_id,
                    alert.monthly_budget_usd,
                    alert.alert_threshold_pct,
                    alert.notify_email,
                    alert.notify_webhook,
                    alert.enabled,
                ),
            )

        return alert

    def check_alerts(self) -> List[Tuple[BudgetAlert, float, float]]:
        """
        Check all budget alerts and return triggered ones.

        Returns:
            List of (alert, current_spend, threshold) tuples
        """
        # Get current month's spending
        now = datetime.now(timezone.utc)
        month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

        with self._connect() as conn:
            # Get all enabled alerts
            alerts = conn.execute("""
                SELECT * FROM budget_alerts WHERE enabled = 1
            """).fetchall()

            triggered = []

            for row in alerts:
                alert = BudgetAlert(
                    id=row["id"],
                    name=row["name"],
                    user_id=row["user_id"],
                    monthly_budget_usd=row["monthly_budget_usd"],
                    alert_threshold_pct=row["alert_threshold_pct"],
                    notify_email=row["notify_email"],
                    notify_webhook=row["notify_webhook"],
                    last_alerted=datetime.fromisoformat(row["last_alerted"])
                    if row["last_alerted"]
                    else None,
                    enabled=row["enabled"],
                )

                # Get spending for this alert's scope
                if alert.user_id:
                    spend_row = conn.execute(
                        """
                        SELECT SUM(cost_usd) as total
                        FROM daily_stats
                        WHERE date >= ? AND user_id = ?
                    """,
                        (month_start.strftime("%Y-%m-%d"), alert.user_id),
                    ).fetchone()
                else:
                    spend_row = conn.execute(
                        """
                        SELECT SUM(cost_usd) as total
                        FROM daily_stats
                        WHERE date >= ?
                    """,
                        (month_start.strftime("%Y-%m-%d"),),
                    ).fetchone()

                current_spend = spend_row["total"] or 0.0
                threshold = alert.monthly_budget_usd * (alert.alert_threshold_pct / 100)

                # Check if triggered
                if current_spend >= threshold:
                    # Don't alert more than once per day
                    if alert.last_alerted and (now - alert.last_alerted) < timedelta(days=1):
                        continue

                    triggered.append((alert, current_spend, threshold))

                    # Update last_alerted
                    conn.execute(
                        """
                        UPDATE budget_alerts SET last_alerted = ? WHERE id = ?
                    """,
                        (now.isoformat(), alert.id),
                    )

            return triggered


# ============================================================
# SINGLETON
# ============================================================

_analytics_store: AnalyticsStore = None


def get_analytics_store() -> AnalyticsStore:
    """Get or create the global analytics store."""
    global _analytics_store
    if _analytics_store is None:
        _analytics_store = AnalyticsStore()
    return _analytics_store


# ============================================================
# AGENT INTEGRATION
# ============================================================


def record_agent_usage(
    user_id: str,
    channel: str,
    tokens_in: int,
    tokens_out: int,
    latency_ms: int,
    model: str = None,
    skill: str = None,
    user_email: str = None,
    success: bool = True,
    error_message: str = None,
):
    """
    Record agent usage from agent.chat().

    Called automatically by agent if analytics is enabled.

    Cost estimation:
        - Claude: $3/M input, $15/M output
        - GPT-4: $10/M input, $30/M output
        - GPT-3.5: $0.50/M input, $1.50/M output
    """
    # Estimate cost based on model
    cost_per_m_in = 3.0  # Default to Claude pricing
    cost_per_m_out = 15.0

    if model:
        model_lower = model.lower()
        if "gpt-4" in model_lower:
            cost_per_m_in = 10.0
            cost_per_m_out = 30.0
        elif "gpt-3.5" in model_lower:
            cost_per_m_in = 0.5
            cost_per_m_out = 1.5
        elif "claude-3-haiku" in model_lower:
            cost_per_m_in = 0.25
            cost_per_m_out = 1.25
        elif "claude-3-sonnet" in model_lower:
            cost_per_m_in = 3.0
            cost_per_m_out = 15.0
        elif "claude-3-opus" in model_lower:
            cost_per_m_in = 15.0
            cost_per_m_out = 75.0

    cost_usd = (tokens_in * cost_per_m_in / 1_000_000) + (tokens_out * cost_per_m_out / 1_000_000)

    store = get_analytics_store()

    event = UsageEvent(
        user_id=user_id,
        user_email=user_email,
        channel=channel,
        event_type=EventType.MESSAGE,
        skill=skill,
        tokens_in=tokens_in,
        tokens_out=tokens_out,
        cost_usd=cost_usd,
        latency_ms=latency_ms,
        model=model,
        success=success,
        error_message=error_message,
    )

    store.record(event)
