"""Post-extraction stale plan filter.

Detects planning-intent claims whose temporal_marker references a date >30 days
in the past. Such claims likely describe plans that were never updated after
completion or abandonment — storing them pollutes the memory graph with stale
future-intent that no longer holds.

Design philosophy: better to miss some stale plans than to falsely flag active
ones. The filter only fires when ALL of these conditions are met:
    1. The predicate is a known planning verb.
    2. temporal is "future" or "current" (not "past" or "permanent").
    3. temporal_marker contains a recognisable month+year that is >30 days old.

Stale claims are **removed** from the list (equivalent to NOOP — the
reconciliation engine never sees them).
"""

from __future__ import annotations

import logging
import re
from datetime import datetime, timezone, timedelta
from typing import Any, List, Optional, Set

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Planning predicates — verbs that signal future intent
# ---------------------------------------------------------------------------
PLANNING_PREDICATES: Set[str] = {
    "plans",
    "proposed",
    "wants",
    "intends",
    "will",
    "will_implement",
    "scheduled",
    "aims",
    "considering",
    "hoping",
    "plans_to",
    "intends_to",
    "wants_to",
    "aims_to",
    "will_build",
    "will_create",
    "will_deploy",
    "will_ship",
    "will_add",
    "will_migrate",
    "hopes_to",
    "needs",
    "needs_to",
}

# ---------------------------------------------------------------------------
# Date parsing helpers
# ---------------------------------------------------------------------------
_MONTH_NAMES = {
    "january": 1, "february": 2, "march": 3, "april": 4,
    "may": 5, "june": 6, "july": 7, "august": 8,
    "september": 9, "october": 10, "november": 11, "december": 12,
    "jan": 1, "feb": 2, "mar": 3, "apr": 4,
    "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12,
}

# Matches "March 2026", "Feb 2025", "january 2026", etc.
_MONTH_YEAR_RE = re.compile(
    r"\b(" + "|".join(_MONTH_NAMES.keys()) + r")\s+(\d{4})\b",
    re.IGNORECASE,
)

# Matches "2026-03", "2025-12", etc.
_ISO_MONTH_RE = re.compile(r"\b(\d{4})-(\d{2})(?!-\d{2})\b")


def _parse_temporal_marker_date(marker: str) -> Optional[datetime]:
    """Try to extract a month+year from a temporal_marker string.

    Returns the first day of the identified month (in UTC) or None if no
    recognisable date is found.  We intentionally do NOT try to parse
    vague markers like "yesterday" or "last week" — only month+year.
    """
    if not marker:
        return None

    # Try "Month Year" patterns first
    m = _MONTH_YEAR_RE.search(marker)
    if m:
        month = _MONTH_NAMES[m.group(1).lower()]
        year = int(m.group(2))
        try:
            return datetime(year, month, 1, tzinfo=timezone.utc)
        except ValueError:
            return None

    # Try ISO-style "YYYY-MM"
    m = _ISO_MONTH_RE.search(marker)
    if m:
        year, month = int(m.group(1)), int(m.group(2))
        try:
            return datetime(year, month, 1, tzinfo=timezone.utc)
        except ValueError:
            return None

    return None


# ---------------------------------------------------------------------------
# Field accessor (works with dicts and dataclasses)
# ---------------------------------------------------------------------------
def _get(obj: Any, key: str, default: str = "") -> Any:
    """Get a field from a dict or dataclass."""
    if isinstance(obj, dict):
        return obj.get(key, default)
    return getattr(obj, key, default)


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
def filter_stale_plans(
    claims: list,
    *,
    now: Optional[datetime] = None,
    stale_threshold_days: int = 30,
) -> list:
    """Remove stale planning claims whose temporal_marker is >threshold days old.

    Args:
        claims: List of claim dicts or ClaimExtraction dataclass objects.
        now: Reference time for staleness check (default: utcnow).
        stale_threshold_days: Number of days after which a plan is considered
                              stale (default: 30).

    Returns:
        Filtered list with stale plan claims removed.
    """
    if now is None:
        now = datetime.now(timezone.utc)

    cutoff = now - timedelta(days=stale_threshold_days)

    filtered: List[Any] = []
    removed = 0
    examples: List[str] = []

    for claim in claims:
        predicate = str(_get(claim, "predicate", "")).lower().strip()
        temporal = str(_get(claim, "temporal", "")).lower().strip()
        temporal_marker = str(_get(claim, "temporal_marker", "") or "")
        category = str(_get(claim, "category", "")).lower().strip()

        # Only consider planning predicates
        if predicate not in PLANNING_PREDICATES:
            filtered.append(claim)
            continue

        # Only flag future/current intent — never past or permanent
        if temporal not in ("future", "current"):
            filtered.append(claim)
            continue

        # Never flag identity or permanent claims regardless of predicate
        if category in ("identity", "permanent"):
            filtered.append(claim)
            continue

        # Check if temporal_marker references a past date
        marker_date = _parse_temporal_marker_date(temporal_marker)
        if marker_date is None:
            # No parseable date → keep (conservative)
            filtered.append(claim)
            continue

        if marker_date < cutoff:
            # Stale plan — remove
            removed += 1
            subject = str(_get(claim, "subject", ""))
            obj_val = str(_get(claim, "object_value", ""))[:60]
            example = f"{subject} {predicate} {obj_val} (marker={temporal_marker})"
            if len(examples) < 5:
                examples.append(example)
            logger.info(
                "Stale plan filtered: %s %s %s (temporal_marker=%s, age > %d days)",
                subject, predicate, obj_val, temporal_marker, stale_threshold_days,
            )
            continue

        filtered.append(claim)

    if removed:
        logger.info(
            "StalePlanFilter removed %d/%d claims. Examples: %s",
            removed, len(claims), "; ".join(examples),
        )

    return filtered
