from __future__ import annotations

import re
from dataclasses import dataclass
from enum import Enum


class PromotionClass(str, Enum):
    AUTO_OK = "auto_ok"
    DISCOURAGED = "discouraged"
    BLOCKED = "blocked"


class PromotionStrictness(str, Enum):
    OFF = "off"
    WARN = "warn"
    ENFORCE = "enforce"


@dataclass(frozen=True)
class PromotionDecision:
    classification: PromotionClass
    reason: str
    secret_like: bool = False


_SECRET_PATTERNS = (
    re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
    re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
    re.compile(r"\bghp_[A-Za-z0-9]{20,}\b"),
    re.compile(r"\bsk-[A-Za-z0-9]{20,}\b"),
    re.compile(r"\bapi[_-]?key\s*[:=]\s*['\"]?[A-Za-z0-9_\-]{12,}", re.IGNORECASE),
    re.compile(r"\bpassword\s*[:=]\s*['\"]?[^\s'\"`]{8,}", re.IGNORECASE),
    re.compile(r"\bbearer\s+[A-Za-z0-9._\-]{20,}", re.IGNORECASE),
)

_DISCOURAGED_PATTERNS = (
    re.compile(r"^\s*remember this\s*$", re.IGNORECASE),
    re.compile(r"^\s*don['\u2018\u2019]?t forget\s*$", re.IGNORECASE),
)

# Episodic one-liners (“today was…”) rarely belong in long-term memory unless they carry a stable fact.
_EPISODIC_ONE_LINER = re.compile(
    r"(?is)^\s*(?:today|yesterday|this morning|tonight|last night|right now)\b[,:\s]+.{1,320}\s*$"
)
_STABILITY_HINTS = re.compile(
    r"\b("
    r"user|prefers|preference|preferences|always|never|usually|"
    r"works\b|employer|role\b|deadline|due\b|project|constraint|constraints|"
    r"goal|goals|allerg|schedule|family|partner|lives in|based in|"
    r"decided|commits to|wants\b|needs\b|requires\b|must\b|don't want"
    r")\b",
    re.IGNORECASE,
)

_MIN_ENTRY_CHARS = 14
_MAX_ENTRY_CHARS = 12_000

_SELF_REFERENTIAL_PATTERNS = (
    re.compile(r"Autonomous Memory Task", re.IGNORECASE),
    re.compile(r"MEMORY\.md Status:", re.IGNORECASE),
    re.compile(r"Heartbeat Check", re.IGNORECASE),
    re.compile(r"\bmemory maintenance\b", re.IGNORECASE),
    re.compile(r"recurring[- ]patterns review", re.IGNORECASE),
    re.compile(r"Memory/Summaries Directory Status", re.IGNORECASE),
    re.compile(r"\bGrowth Check\b", re.IGNORECASE),
    re.compile(r"\bheartbeat\b.*\b(?:task|run|cycle|check)\b", re.IGNORECASE),
    re.compile(r"\bconsolidation (?:engine|system|process)\b", re.IGNORECASE),
    re.compile(r"\binsights\.md\b", re.IGNORECASE),
    re.compile(r"\brecurring-patterns\.md\b", re.IGNORECASE),
    re.compile(r"\bSESSION-STATE\.md\b.*\b(?:status|review|check)\b", re.IGNORECASE),
    re.compile(r"\bmemory system\b", re.IGNORECASE),
    re.compile(r"\bfile (?:status|check|review)\b.*\b(?:memory|workspace)\b", re.IGNORECASE),
)


def _leading_line_for_episodic(text: str) -> str:
    """First logical line, with a single leading markdown bullet stripped if present."""
    first = (text or "").strip().split("\n", 1)[0].strip()
    return re.sub(r"^\s*[-*]\s+", "", first, count=1).strip()


def classify_memory_entry(text: str) -> PromotionDecision:
    entry = (text or "").strip()
    if not entry:
        return PromotionDecision(PromotionClass.DISCOURAGED, reason="empty_entry")

    for pattern in _SECRET_PATTERNS:
        if pattern.search(entry):
            return PromotionDecision(PromotionClass.BLOCKED, reason="secret_like_pattern", secret_like=True)

    for pattern in _SELF_REFERENTIAL_PATTERNS:
        if pattern.search(entry):
            return PromotionDecision(PromotionClass.BLOCKED, reason="self_referential")

    for pattern in _DISCOURAGED_PATTERNS:
        if pattern.search(entry):
            return PromotionDecision(PromotionClass.DISCOURAGED, reason="too_vague")

    if len(entry) < _MIN_ENTRY_CHARS:
        return PromotionDecision(PromotionClass.DISCOURAGED, reason="too_brief")
    if len(entry) > _MAX_ENTRY_CHARS:
        return PromotionDecision(PromotionClass.DISCOURAGED, reason="too_long")

    # Short, diary-style moments without a durable fact → suggest session note / chat, not MEMORY.
    if len(entry) < 480 and entry.count("\n") <= 2:
        probe = _leading_line_for_episodic(entry)
        if _EPISODIC_ONE_LINER.match(probe) and not _STABILITY_HINTS.search(entry):
            return PromotionDecision(PromotionClass.DISCOURAGED, reason="episodic_moment")

    return PromotionDecision(PromotionClass.AUTO_OK, reason="default_allow")


def strictness_from_semantic_pref(memory_semantic_strictness: str | None) -> PromotionStrictness:
    val = str(memory_semantic_strictness or "").strip().lower()
    if val == "enforce_promotion":
        return PromotionStrictness.ENFORCE
    if val == "warn_promotion":
        return PromotionStrictness.WARN
    return PromotionStrictness.OFF


def blocked_memory_message(secret_like: bool) -> str:
    if secret_like:
        return (
            "I can't store that in MEMORY.md because it appears to contain sensitive credentials or secrets. "
            "Please remove secrets before asking me to store it, and use app settings/keychain for account secrets."
        )
    return "I can't store that in MEMORY.md. Please rewrite it as a non-sensitive, durable memory."


def discouraged_memory_message(reason: str) -> str:
    """User-facing refusal when ``memory_semantic_strictness`` is enforce and entry is discouraged."""
    r = str(reason or "").strip()
    if r == "too_brief":
        return (
            "This is too thin to store as long-term memory. Add one concrete, stable fact "
            "(preference, constraint, goal, or relationship to the user) in a full sentence."
        )
    if r == "too_long":
        return (
            "That block is too long for a single memory append. Summarize into a few short, load-bearing facts "
            "or split across multiple dated entries / shards."
        )
    if r == "episodic_moment":
        return (
            "That reads like a fleeting moment, not a durable fact. Rewrite as what changes future behavior "
            "(e.g. what the user prefers, decided, or needs next time) or keep it in session notes instead."
        )
    if r == "too_vague":
        return (
            "That's too vague to store. Replace with a specific claim you can act on later "
            "(who/what/when as appropriate, minus secrets)."
        )
    return (
        "Under your Memory promotion setting (enforce), this doesn't qualify as long-term memory yet. "
        "Rewrite as a concise stable fact about the user, their constraints, or recurring preferences."
    )
