"""Knowledge entry categorizer with category-based TTLs.

Classifies knowledge entries into freshness categories that determine how
quickly entries need re-confirmation. Entries that aren't re-confirmed within
their TTL auto-transition to 'stale'.

Categories and TTLs:
    INFRASTRUCTURE  (7 days)  — IPs, ports, services, deploy paths
    MEETING_INTEL   (14 days) — Meeting notes, commitments, action items
    ARCHITECTURE    (14 days) — System design, component relationships
    CONFIGURATION   (14 days) — Settings, env vars, feature flags
    RUNBOOK         (14 days) — Operational procedures, recurring tasks
    DESIGN_DECISION (30 days) — UI/UX choices, brand, aesthetic direction
    PROCESS         (30 days) — Deployment steps, workflows, checklists
    CORRECTION      (30 days) — Bug fixes, gotchas (highly perishable)
    BUSINESS_DECISION(60 days) — Pricing, positioning, launch timing
    PRODUCT_INSIGHT (60 days) — User feedback, market signals, competitive intel
    PATTERN         (60 days) — Design patterns, best practices
    PREFERENCE      (90 days) — Working style, tool preferences, communication style
    DOMAIN_EXPERTISE(90 days) — Standards, algorithms, protocol details

When an entry matches multiple categories, the shortest TTL wins
(most conservative approach).
"""
from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True)
class CategorySpec:
    """Definition of a knowledge category with its TTL and matching rules."""
    name: str
    ttl_days: int
    keywords: frozenset[str]
    domains: frozenset[str]
    entry_types: frozenset[str]


# Category definitions ordered by TTL (shortest first for multi-match tiebreaker)
CATEGORIES: list[CategorySpec] = [
    CategorySpec(
        name="infrastructure",
        ttl_days=7,
        keywords=frozenset({
            "ip address", "port ", "node ", "binary",
            "launchctl", "launchd", "tmux",
            "hostname", "server ", "machine", "cluster",
            "container", "docker", "pod ", "k8s", "kubernetes",
            "tailscale", "ssh ", "daemon", "systemd",
            "pid ", "socket", "listen", "bind ",
        }),
        domains=frozenset({"infrastructure"}),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="meeting_intel",
        ttl_days=14,
        keywords=frozenset({
            "meeting", "call", "discussion", "action item", "commitment",
            "follow-up", "follow up", "agenda", "attendee",
            "decided", "agreed", "next steps", "takeaway",
            "sync", "standup", "1:1", "one-on-one",
        }),
        domains=frozenset(),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="architecture",
        ttl_days=14,
        keywords=frozenset({
            "architecture", "system design", "data flow", "protocol",
            "component", "module", "microservice", "monolith",
            "event-driven", "message queue", "pub/sub", "pubsub",
            "service mesh", "abstraction layer",
        }),
        domains=frozenset(),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="configuration",
        ttl_days=14,
        keywords=frozenset({
            "config", "setting", "env", "flag", "limit", "threshold",
            "parameter", "environment variable", "feature flag",
            "rate limit", "timeout", "retry", "tuning",
            "pragma", "option",
        }),
        domains=frozenset(),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="runbook",
        ttl_days=14,
        keywords=frozenset({
            "runbook", "playbook", "checklist", "how to", "howto",
            "step-by-step", "procedure", "recurring task",
            "rollback", "backup", "restore", "deploy procedure",
            "restart procedure", "upgrade steps",
        }),
        domains=frozenset({"devops", "infrastructure"}),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="design_decision",
        ttl_days=30,
        keywords=frozenset({
            "ui", "ux", "design decision", "brand", "color",
            "typography", "layout", "responsive", "mobile",
            "aesthetic", "visual", "wireframe", "mockup",
            "component design", "icon", "animation",
        }),
        domains=frozenset({"creativity", "dashboard"}),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="correction",
        ttl_days=30,
        keywords=frozenset({
            "bug", "fix", "workaround", "gotcha", "pitfall",
            "mistake", "wrong", "broken", "issue", "regression",
        }),
        domains=frozenset(),
        entry_types=frozenset({"correction", "gotcha"}),
    ),
    CategorySpec(
        name="process",
        ttl_days=30,
        keywords=frozenset({
            "deploy", "install", "upgrade", "restart", "procedure",
            "step", "workflow", "migration",
            "guide", "tutorial",
        }),
        domains=frozenset(),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="business_decision",
        ttl_days=60,
        keywords=frozenset({
            "pricing", "price", "revenue", "positioning", "launch",
            "go-to-market", "gtm", "target market", "competitor",
            "business model", "monetization", "tier", "plan",
            "customer segment", "sled", "enterprise",
            "strategy", "roadmap", "milestone",
        }),
        domains=frozenset({"business", "finance"}),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="product_insight",
        ttl_days=60,
        keywords=frozenset({
            "user feedback", "feature request", "pain point",
            "market signal", "competitive intel", "displacement",
            "user research", "discovery", "demo feedback",
            "churn", "adoption", "onboarding friction",
        }),
        domains=frozenset({"product", "salesos"}),
        entry_types=frozenset(),
    ),
    CategorySpec(
        name="pattern",
        ttl_days=60,
        keywords=frozenset({
            "pattern", "anti-pattern", "best practice", "idiom",
            "principle", "convention", "style", "approach",
            "recurring", "template", "boilerplate",
        }),
        domains=frozenset(),
        entry_types=frozenset({"pattern"}),
    ),
    CategorySpec(
        name="preference",
        ttl_days=90,
        keywords=frozenset({
            "prefer", "preference", "working style", "workflow",
            "tool choice", "communication style", "habit",
            "routine", "efficiency", "productivity",
        }),
        domains=frozenset({"personal", "productivity"}),
        entry_types=frozenset({"habit", "values"}),
    ),
    CategorySpec(
        name="domain_expertise",
        ttl_days=90,
        keywords=frozenset({
            "algorithm", "standard", "protocol", "api", "library",
            "specification", "rfc", "fips", "nist", "iso",
            "cryptography", "encryption", "hash", "tls", "ssl",
            "oauth", "jwt", "saml", "oidc",
        }),
        domains=frozenset(),
        entry_types=frozenset({"expertise", "reference"}),
    ),
]

# Build a lookup map: category_name -> CategorySpec
CATEGORY_MAP: dict[str, CategorySpec] = {c.name: c for c in CATEGORIES}

# Valid category names
VALID_CATEGORIES: frozenset[str] = frozenset(CATEGORY_MAP.keys())

# Default category when no signals match
DEFAULT_CATEGORY = "domain_expertise"
DEFAULT_TTL = 90


def classify_entry(
    entry: dict[str, Any],
    *,
    manual_override: str | None = None,
) -> tuple[str, int]:
    """Classify a knowledge entry into a freshness category.

    Uses a multi-signal approach:
    1. Manual override (if provided and valid)
    2. Entry type mapping (correction -> CORRECTION, pattern -> PATTERN)
    3. Domain mapping (infrastructure domain -> INFRASTRUCTURE)
    4. Keyword matching in title + content (shortest-TTL category wins)
    5. Default: DOMAIN_EXPERTISE (90 days)

    Args:
        entry: Knowledge entry dict with at least 'title', 'content',
               and optionally 'entry_type', 'domain'.
        manual_override: Explicit category name; skips auto-classification.

    Returns:
        Tuple of (category_name, ttl_days).
    """
    # Manual override takes precedence
    if manual_override and manual_override in VALID_CATEGORIES:
        spec = CATEGORY_MAP[manual_override]
        return spec.name, spec.ttl_days

    entry_type = (entry.get("entry_type") or "").lower()
    domain = (entry.get("domain") or "").lower()
    title = (entry.get("title") or "").lower()
    content = (entry.get("content") or "").lower()
    combined = f"{title} {content}"

    # Score each category; track all matches
    matches: list[CategorySpec] = []

    for spec in CATEGORIES:
        score = _score_category(spec, entry_type, domain, combined)
        if score > 0:
            matches.append(spec)

    if not matches:
        return DEFAULT_CATEGORY, DEFAULT_TTL

    # Shortest TTL wins (most conservative)
    best = min(matches, key=lambda s: s.ttl_days)
    return best.name, best.ttl_days


def _score_category(
    spec: CategorySpec,
    entry_type: str,
    domain: str,
    combined_text: str,
) -> int:
    """Score how well an entry matches a category.

    Returns a positive score if there's a match, 0 otherwise.
    Higher scores indicate stronger matches but we use shortest-TTL
    tiebreaking, not score-based.
    """
    score = 0

    # Entry type match (strong signal)
    if entry_type and entry_type in spec.entry_types:
        score += 3

    # Domain match (strong signal)
    if domain and domain in spec.domains:
        score += 3

    # Keyword match (moderate signal, require at least 1 keyword)
    keyword_hits = 0
    for kw in spec.keywords:
        if kw in combined_text:
            keyword_hits += 1
    if keyword_hits >= 1:
        score += keyword_hits

    return score


def classify_batch(
    entries: list[dict[str, Any]],
) -> list[dict[str, Any]]:
    """Classify a batch of entries, returning classification results.

    Args:
        entries: List of knowledge entry dicts.

    Returns:
        List of dicts with 'id', 'knowledge_category', 'ttl_days' for each entry.
    """
    results = []
    for entry in entries:
        category, ttl = classify_entry(entry)
        results.append({
            "id": entry.get("id", ""),
            "knowledge_category": category,
            "ttl_days": ttl,
        })
    return results


def get_ttl_for_category(category: str) -> int:
    """Look up the TTL for a given category name.

    Args:
        category: Category name (e.g., 'infrastructure').

    Returns:
        TTL in days, or DEFAULT_TTL if category is unknown.
    """
    spec = CATEGORY_MAP.get(category)
    return spec.ttl_days if spec else DEFAULT_TTL
