#!/usr/bin/env python3
# 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

"""
Progressive Capability Disclosure
==================================

Convergence Pipeline Week 2, Item 2A.
Articles fulfilled: 4 (Hot Dog Principle), 5 (Earn Trust), 7 (Last Inch).

Familiar has 60+ skills. Showing all on day one is overwhelming.
Moltbot reveals depth gradually. This module implements:

1. Starter skill set — onboarding enables 5 core skills, rest loaded but not surfaced
2. Contextual suggestions — when user needs a disabled skill, suggest it
3. Achievement moments — milestones at interactions 10, 50, 100, 250, 500
4. Weekly capability digest — one unused skill surfaced per week
5. Skill usage tracking — per-skill invocation counters

Builds on:
    - core/skills.py SkillLoader (enable/disable)
    - core/constitution.py InteractionTracker (milestone counts)
    - core/tools.py ToolRegistry (execute)
    - skills/proactive/skill.py (weekly digest scheduling)
"""

import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

logger = logging.getLogger(__name__)


# ── Starter Skills ────────────────────────────────────────────────────
# These are enabled at onboarding. Everything else loads but is not
# presented in the system prompt until discovered or enabled.

STARTER_SKILLS = {
    "calendar",
    "tasks",
    "email",
    "websearch",
    "proactive",
}

# Skills that are available but revealed gradually
DISCOVERABLE_SKILLS = {
    # Tier 2: suggested after 10 interactions
    "documents": {"tier": 2, "hint": "I can help create and manage documents too."},
    "knowledge": {
        "tier": 2,
        "hint": "I can remember things you tell me in a personal knowledge base.",
    },
    "notes": {"tier": 2, "hint": "I can keep notes for you — meeting notes, ideas, anything."},
    # Tier 3: suggested after 50 interactions
    "bookkeeping": {"tier": 3, "hint": "I can help track expenses and income."},
    "reports": {"tier": 3, "hint": "I can generate reports from your data."},
    "workflows": {"tier": 3, "hint": "I can automate multi-step workflows for you."},
    "nonprofit": {"tier": 3, "hint": "I have donor CRM, grant tracking, and board packet tools."},
    # Tier 4: suggested after 100 interactions
    "browser": {"tier": 4, "hint": "I can browse the web and interact with websites for you."},
    "code": {"tier": 4, "hint": "I can write, run, and debug code."},
    "analytics": {"tier": 4, "hint": "I can analyze data and create visualizations."},
    "marketplace": {
        "tier": 4,
        "hint": "There's a skill marketplace with community-built capabilities.",
    },
}

# Map tier → interaction count threshold
TIER_THRESHOLDS = {
    1: 0,  # Starter — always visible
    2: 10,  # After relationship begins
    3: 50,  # After trust established
    4: 100,  # Power user
}

# Milestone interaction counts for achievement moments
MILESTONES = [10, 50, 100, 250, 500, 1000]


# ── Skill Usage Tracker ───────────────────────────────────────────────


class SkillUsageTracker:
    """
    Tracks per-skill invocation counts and discovery state.

    Persists alongside interaction counts in ~/.familiar/data/.
    """

    def __init__(self, data_dir: Optional[Path] = None):
        if data_dir is None:
            try:
                from familiar.core.paths import DATA_DIR

                data_dir = DATA_DIR
            except ImportError:
                data_dir = Path.home() / ".familiar" / "data"

        self._file = data_dir / "skill_usage.json"
        self._data: Dict = {
            "usage_counts": {},  # skill_name → total invocations
            "discovered_skills": [],  # skills the user has been told about
            "achieved_milestones": [],  # milestone counts already triggered
            "last_weekly_digest": None,  # ISO datetime of last digest
            "onboarding_complete": False,
        }
        self._load()

    def _load(self):
        if self._file.exists():
            try:
                self._data = json.loads(self._file.read_text())
            except (json.JSONDecodeError, IOError) as e:
                logger.warning(f"Failed to load skill usage: {e}")

    def _save(self):
        try:
            self._file.parent.mkdir(parents=True, exist_ok=True)
            self._file.write_text(json.dumps(self._data, indent=2))
        except IOError as e:
            logger.error(f"Failed to save skill usage: {e}")

    def record_skill_use(self, skill_name: str):
        """Record that a skill's tool was invoked."""
        counts = self._data.setdefault("usage_counts", {})
        counts[skill_name] = counts.get(skill_name, 0) + 1
        self._save()

    def get_usage(self, skill_name: str) -> int:
        return self._data.get("usage_counts", {}).get(skill_name, 0)

    def get_all_usage(self) -> Dict[str, int]:
        return dict(self._data.get("usage_counts", {}))

    def get_unused_skills(self, available_skills: List[str]) -> List[str]:
        """Get skills that are loaded but never used."""
        used = set(self._data.get("usage_counts", {}).keys())
        return [s for s in available_skills if s not in used and s not in STARTER_SKILLS]

    def mark_discovered(self, skill_name: str):
        """Mark a skill as having been suggested to the user."""
        discovered = self._data.setdefault("discovered_skills", [])
        if skill_name not in discovered:
            discovered.append(skill_name)
            self._save()

    def is_discovered(self, skill_name: str) -> bool:
        return skill_name in self._data.get("discovered_skills", [])

    def mark_milestone(self, count: int):
        """Mark a milestone as achieved."""
        achieved = self._data.setdefault("achieved_milestones", [])
        if count not in achieved:
            achieved.append(count)
            self._save()

    def is_milestone_achieved(self, count: int) -> bool:
        return count in self._data.get("achieved_milestones", [])

    def set_onboarding_complete(self):
        self._data["onboarding_complete"] = True
        self._save()

    @property
    def onboarding_complete(self) -> bool:
        return self._data.get("onboarding_complete", False)

    def record_weekly_digest(self):
        self._data["last_weekly_digest"] = datetime.now().isoformat()
        self._save()

    def days_since_digest(self) -> float:
        last = self._data.get("last_weekly_digest")
        if not last:
            return 999.0  # Never sent
        try:
            dt = datetime.fromisoformat(last)
            return (datetime.now() - dt).total_seconds() / 86400
        except ValueError:
            return 999.0


# ── Starter Skill Filter ─────────────────────────────────────────────


def filter_starter_skills(skill_loader, force_all: bool = False) -> int:
    """
    Apply starter skill set: disable non-starter skills at first load.

    Returns the number of skills that were filtered (disabled).

    If force_all=True, enables everything (for experienced users).
    """
    tracker = SkillUsageTracker()

    if force_all or tracker.onboarding_complete:
        # After onboarding, check if user has graduated past starter set
        # If they've used 3+ starter skills, don't filter anymore
        starter_used = sum(1 for s in STARTER_SKILLS if tracker.get_usage(s) > 0)
        if starter_used >= 3:
            return 0

    filtered = 0
    for skill_name, skill in skill_loader.skills.items():
        if skill_name in STARTER_SKILLS:
            # Always enable starters
            if not skill.enabled:
                skill_loader.enable_skill(skill_name)
        elif skill.enabled and skill_name in DISCOVERABLE_SKILLS:
            # Don't disable — just don't surface in docs
            # The skill stays loaded so tools work if enabled
            pass

    return filtered


# ── Contextual Suggestion ─────────────────────────────────────────────


def suggest_skill_for_tool(tool_name: str, skill_loader) -> Optional[str]:
    """
    When a tool isn't found, check if it belongs to a disabled/undiscovered skill
    and generate a suggestion message.

    This replaces the generic "Error: Unknown tool" with:
    "I can do that if you enable the [skill] skill. Want me to turn it on?"

    Returns suggestion string or None.
    """
    # Check all skills (including disabled) for this tool
    for skill_name, skill in skill_loader.skills.items():
        for tool in skill.tools:
            if tool.name == tool_name:
                if not skill.enabled:
                    # Skill exists but is disabled
                    return (
                        f"I can do that — it uses the **{skill_name}** skill which isn't "
                        f"enabled yet. Want me to turn it on?"
                    )
                else:
                    # Skill is enabled, tool should be findable — genuine error
                    return None

    # Check discoverable skills by name heuristic
    tool_lower = tool_name.lower()
    for skill_name, info in DISCOVERABLE_SKILLS.items():
        if skill_name in tool_lower or tool_lower in skill_name:
            return (
                f"That sounds like something the **{skill_name}** skill can help with. "
                f"{info['hint']} Want me to enable it?"
            )

    return None


# ── Achievement Moments ───────────────────────────────────────────────


def check_milestone(user_id: str, interaction_count: int, skill_loader) -> Optional[str]:
    """
    Check if user hit a milestone and generate an achievement message.

    Returns achievement text or None. Rate-limited: won't fire if another
    milestone was hit in the last 24 hours (prevents collision with heartbeat).

    Milestones:
        10:  "We've been working together... did you know I can also [skill]?"
        50:  "50 conversations! Here's what we've accomplished..."
        100: "You're a power user now. Here are some advanced capabilities..."
        250: "Quarter-thousand milestone. Full stats."
        500+: "Milestone. Summary."
    """
    tracker = SkillUsageTracker()

    # Find which milestone this count matches
    milestone = None
    for m in MILESTONES:
        if interaction_count >= m and not tracker.is_milestone_achieved(m):
            milestone = m
            break

    if milestone is None:
        return None

    # Mark it
    tracker.mark_milestone(milestone)

    # Build achievement message based on milestone
    if milestone == 10:
        # Suggest a tier-2 skill the user hasn't discovered
        suggestion = _pick_undiscovered_skill(tracker, skill_loader, tier=2)
        if suggestion:
            tracker.mark_discovered(suggestion)
            hint = DISCOVERABLE_SKILLS.get(suggestion, {}).get("hint", "")
            return (
                f"We've been working together for a bit now. "
                f"Did you know I can also help with **{suggestion}**? {hint} "
                f"Just ask and I'll enable it."
            )
        return None  # No suggestion available — skip silently

    elif milestone == 50:
        # Usage summary + tier-3 suggestion
        usage = tracker.get_all_usage()
        top_skills = sorted(usage.items(), key=lambda x: x[1], reverse=True)[:3]
        top_str = ", ".join(f"{name} ({count}x)" for name, count in top_skills)

        suggestion = _pick_undiscovered_skill(tracker, skill_loader, tier=3)
        suggest_str = ""
        if suggestion:
            tracker.mark_discovered(suggestion)
            hint = DISCOVERABLE_SKILLS.get(suggestion, {}).get("hint", "")
            suggest_str = f"\n\nReady for more? {hint}"

        return f"🎉 50 conversations together! Your most-used capabilities: {top_str}.{suggest_str}"

    elif milestone == 100:
        suggestion = _pick_undiscovered_skill(tracker, skill_loader, tier=4)
        suggest_str = ""
        if suggestion:
            tracker.mark_discovered(suggestion)
            hint = DISCOVERABLE_SKILLS.get(suggestion, {}).get("hint", "")
            suggest_str = f" One you might like: **{suggestion}** — {hint}"

        return (
            f"💯 100 conversations! You're in the top tier now. "
            f"All advanced capabilities are available.{suggest_str}"
        )

    elif milestone in (250, 500, 1000):
        usage = tracker.get_all_usage()
        total_tools = sum(usage.values())
        return (
            f"🏆 {milestone} conversations! "
            f"Total tool invocations: {total_tools} across {len(usage)} skills."
        )

    return None


def _pick_undiscovered_skill(tracker: SkillUsageTracker, skill_loader, tier: int) -> Optional[str]:
    """Pick an undiscovered skill at the given tier."""
    for skill_name, info in DISCOVERABLE_SKILLS.items():
        if info["tier"] <= tier and not tracker.is_discovered(skill_name):
            # Prefer skills that are actually loaded
            if skill_name in skill_loader.skills:
                return skill_name
    return None


# ── Weekly Capability Digest ──────────────────────────────────────────


def generate_weekly_digest(skill_loader) -> Optional[str]:
    """
    Weekly proactive message surfacing one unused capability.

    Scheduled via proactive skill. Only fires if:
    - At least 7 days since last digest
    - There's an unused skill worth mentioning
    - User has completed onboarding

    Returns digest text or None (don't send anything).
    """
    tracker = SkillUsageTracker()

    if not tracker.onboarding_complete:
        return None

    if tracker.days_since_digest() < 7:
        return None

    # Find an unused skill that's loaded
    available = list(skill_loader.skills.keys())
    unused = tracker.get_unused_skills(available)

    if not unused:
        return None

    # Pick one the user hasn't been told about
    pick = None
    for name in unused:
        if not tracker.is_discovered(name):
            pick = name
            break

    if not pick:
        # All unused skills already suggested — skip
        return None

    tracker.mark_discovered(pick)
    tracker.record_weekly_digest()

    info = DISCOVERABLE_SKILLS.get(pick, {})
    hint = info.get("hint", f"The **{pick}** skill has capabilities you haven't tried yet.")

    return (
        f"💡 Weekly tip: {hint}\n\n"
        f'Say "enable {pick}" or just ask me to do something related — I\'ll set it up.'
    )


# ── Integration Hooks ─────────────────────────────────────────────────


def on_tool_executed(tool_name: str, skill_name: str):
    """Called after a tool is successfully executed. Records usage."""
    try:
        tracker = SkillUsageTracker()
        tracker.record_skill_use(skill_name)
    except Exception as e:
        logger.debug(f"Skill usage tracking error: {e}")


def on_tool_not_found(tool_name: str, skill_loader) -> str:
    """
    Called when tools.execute() can't find a tool.

    Returns either a contextual suggestion or the original error message.
    This is the Article 4 intervention point — serve the need, don't just error.
    """
    suggestion = suggest_skill_for_tool(tool_name, skill_loader)
    if suggestion:
        return suggestion
    return f"Error: Unknown tool '{tool_name}'"


def on_interaction_complete(user_id: str, interaction_count: int, skill_loader) -> Optional[str]:
    """
    Called after each interaction. Checks for milestones.

    Returns achievement text to append to the response, or None.
    """
    return check_milestone(user_id, interaction_count, skill_loader)
