# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# Licensed under the MIT License

"""
Email Classification Training Data Store.

Accumulates confirmed classifications (thumbs up) and corrections
(thumbs down → new category) as training pairs for the TF-IDF model.

Training pairs are stored as {subject, from_email, category, source}:
- source="confirmed" — user clicked thumbs up (classifier was right)
- source="corrected" — user changed the category (classifier was wrong)
- source="high_confidence" — classifier was >80% confident (auto-harvested)

The TF-IDF model (Sprint 4) trains on these pairs when enough accumulate.

State persisted at: ~/.familiar/data/email_training_pairs.json
"""

import json
import logging
import threading
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)

_lock = threading.Lock()


def _data_dir() -> Path:
    try:
        from familiar.core.paths import DATA_DIR
        return DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


def _training_path() -> Path:
    return _data_dir() / "email_training_pairs.json"


def _load() -> dict:
    path = _training_path()
    if path.exists():
        try:
            with open(path) as f:
                return json.load(f)
        except (json.JSONDecodeError, OSError):
            pass
    return {"pairs": [], "stats": {"confirmed": 0, "corrected": 0, "high_confidence": 0}, "version": 1}


def _save(data: dict):
    path = _training_path()
    with _lock:
        path.parent.mkdir(parents=True, exist_ok=True)
        tmp = path.with_suffix(".tmp")
        tmp.write_text(json.dumps(data, indent=2, default=str))
        tmp.replace(path)


def record_rating(from_email: str, category: str, correct: bool):
    """Record a user rating (thumbs up/down) as a training pair.

    Thumbs up: confirms the current category for this sender.
    Thumbs down: the feedback endpoint handles the correction separately.
    """
    data = _load()

    if correct:
        # Thumbs up — confirm this classification
        data["pairs"].append({
            "from_email": from_email,
            "category": category,
            "source": "confirmed",
            "timestamp": datetime.now().isoformat(),
        })
        data["stats"]["confirmed"] = data["stats"].get("confirmed", 0) + 1

        # Also learn the sender into triage (same as correction path)
        try:
            from familiar.skills.triage.skill import _load_triage_data, _save_triage_data

            triage = _load_triage_data()
            triage.setdefault("sender_categories", {})[from_email] = category
            _save_triage_data(triage)
        except Exception:
            pass
    else:
        # Thumbs down — record that the classification was wrong
        # The actual correction comes via /api/email/feedback
        data["stats"]["corrected"] = data["stats"].get("corrected", 0) + 1

    # Cap at 5000 pairs
    if len(data["pairs"]) > 5000:
        data["pairs"] = data["pairs"][-5000:]

    _save(data)


def record_correction(from_email: str, old_category: str, new_category: str, subject: str = ""):
    """Record a correction (from feedback endpoint) as a training pair."""
    data = _load()
    data["pairs"].append({
        "from_email": from_email,
        "subject": subject,
        "old_category": old_category,
        "category": new_category,
        "source": "corrected",
        "timestamp": datetime.now().isoformat(),
    })
    data["stats"]["corrected"] = data["stats"].get("corrected", 0) + 1

    if len(data["pairs"]) > 5000:
        data["pairs"] = data["pairs"][-5000:]

    _save(data)


def harvest_high_confidence(classified_emails: list, min_confidence: float = 0.80):
    """Auto-harvest high-confidence classifications as training pairs.

    Called after each sort. Emails where the scorer was >80% confident
    are treated as medium-quality training data.
    """
    data = _load()
    existing_emails = {p.get("from_email") for p in data["pairs"]}
    added = 0

    for em in classified_emails:
        conf = em.get("confidence", 0)
        from_email = em.get("from_email", "")
        category = em.get("category", "Other")

        if conf >= min_confidence and from_email and from_email not in existing_emails:
            data["pairs"].append({
                "from_email": from_email,
                "subject": em.get("subject", ""),
                "category": category,
                "source": "high_confidence",
                "confidence": conf,
                "timestamp": datetime.now().isoformat(),
            })
            existing_emails.add(from_email)
            added += 1

    if added:
        data["stats"]["high_confidence"] = data["stats"].get("high_confidence", 0) + added
        if len(data["pairs"]) > 5000:
            data["pairs"] = data["pairs"][-5000:]
        _save(data)
        logger.info("Harvested %d high-confidence training pairs", added)

    return added


def get_training_pairs(source: str = None) -> list:
    """Get training pairs, optionally filtered by source."""
    data = _load()
    pairs = data.get("pairs", [])
    if source:
        pairs = [p for p in pairs if p.get("source") == source]
    return pairs


def get_stats() -> dict:
    """Get training data statistics."""
    data = _load()
    return {
        "total_pairs": len(data.get("pairs", [])),
        "confirmed": data.get("stats", {}).get("confirmed", 0),
        "corrected": data.get("stats", {}).get("corrected", 0),
        "high_confidence": data.get("stats", {}).get("high_confidence", 0),
        "ready_for_model": len(data.get("pairs", [])) >= 50,
    }
