#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Shared IO helpers: JSONL entry loader + processed-hash tracking."""
import hashlib
import json
import logging
from datetime import datetime, timezone
from pathlib import Path

from pulse.core.brain_utils import (
    HIPPOCAMPUS_DIR,
    LESSONS_DIR, PATTERNS_DIR, FAILS_DIR,
    LESSONS_JSONL, FAILS_JSONL, PATTERNS_JSONL,
    read_jsonl_entries,
)
from pulse.core.brain_io import _acquire

STATE_DIR = Path(HIPPOCAMPUS_DIR).parent / "state"

# Map MD stub paths to their JSONL counterparts
_MD_TO_JSONL = {
    LESSONS_DIR / "auto_lessons.md": LESSONS_JSONL,
    PATTERNS_DIR / "auto_patterns.md": PATTERNS_JSONL,
    FAILS_DIR / "auto_fails.md": FAILS_JSONL,
}

# Map JSONL paths to SQLite table names
_JSONL_TO_TABLE = {
    LESSONS_JSONL: "lessons",
    FAILS_JSONL: "failures",
    PATTERNS_JSONL: "patterns",
}

# Also map by filename stem for private_lessons.jsonl etc.
_STEM_TO_TABLE = {
    "auto_lessons": "lessons",
    "auto_fails": "failures",
    "auto_patterns": "patterns",
    "private_lessons": "private",
}

PROCESSED_HASHES_FILE = STATE_DIR / "consolidation_hashes.json"
MAX_TRACKED_HASHES = 5000  # Bounded ~200KB max
HASH_EXPIRY_DAYS = 14  # P3-5: Hashes older than 14 days expire so items can be re-reconsolidated


def _rows_to_entries(rows) -> list:
    """Convert SQLite Row objects or dicts to the standard entry format."""
    entries = []
    for r in rows:
        if hasattr(r, "keys"):
            r = dict(r)
        title = (r.get("title") or r.get("content", ""))[:200]
        entries.append({
            "title": title,
            "conf": r.get("confidence", 0.5),
            "salience": r.get("salience", 2.0),
            "domain": r.get("domain", "general"),
            "date": (r.get("timestamp") or "")[:10],
            "source": r.get("agent", "unknown"),
            "consolidation_count": r.get("consolidation_count", 1),
            "fingerprint": r.get("fingerprint", ""),
            "_from_jsonl": True,
        })
    return entries


def _get_entries_for_file(md_file):
    """SQLite-first loader: returns list of {title, conf, ...} dicts."""
    jsonl_path = _MD_TO_JSONL.get(md_file)
    if not jsonl_path:
        return []

    # Determine SQLite table name
    table = _JSONL_TO_TABLE.get(jsonl_path)
    if table is None:
        stem = Path(jsonl_path).stem
        table = _STEM_TO_TABLE.get(stem)

    # Try SQLite first
    if table:
        try:
            from pulse.core.brain_db import get_conn
            conn = get_conn()
            rows = conn.execute(f"SELECT * FROM {table}").fetchall()
            if rows:
                return _rows_to_entries(rows)
        except Exception:
            pass

    # Fall back to JSONL if SQLite returned empty or failed
    rows = read_jsonl_entries(jsonl_path)
    if rows:
        return _rows_to_entries(rows)
    return []


def _load_processed_hashes() -> dict:
    """Load processed hashes as dict mapping hash → ISO timestamp.

    P3-5: Entries older than HASH_EXPIRY_DAYS are filtered out so items
    can be re-reconsolidated after 14 days (prevents permanent suppression).
    """
    if PROCESSED_HASHES_FILE.exists():
        try:
            data = json.loads(PROCESSED_HASHES_FILE.read_text(encoding="utf-8"))
            raw = data.get("hashes", {})
            # Legacy format: list of hashes (no timestamps) — promote with epoch 0
            if isinstance(raw, list):
                raw = {h: "1970-01-01T00:00:00+00:00" for h in raw}
            # P3-5: Filter expired entries
            now = datetime.now(timezone.utc)
            hashes = {}
            for h, ts in raw.items():
                try:
                    age_days = (now - datetime.fromisoformat(ts)).days
                    if age_days < HASH_EXPIRY_DAYS:
                        hashes[h] = ts
                except Exception:
                    pass  # Drop malformed timestamps
            return hashes
        except Exception:
            pass
    return {}


def _save_processed_hashes(hashes: dict):
    """Save bounded hashes (hash → timestamp), evicting oldest over MAX_TRACKED_HASHES.

    P3-5: Values are ISO timestamps; new entries without a timestamp are stamped now.
    """
    now_ts = datetime.now(timezone.utc).isoformat()
    # Stamp any legacy None entries
    hashes = {h: (ts if ts else now_ts) for h, ts in hashes.items()}
    while len(hashes) > MAX_TRACKED_HASHES:
        del hashes[next(iter(hashes))]
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    with _acquire(PROCESSED_HASHES_FILE):
        PROCESSED_HASHES_FILE.write_text(
            json.dumps({
                "hashes": hashes,
                "count": len(hashes),
                "updated": now_ts,
            }),
            encoding="utf-8",
        )


def _entry_hash(text: str) -> str:
    """MD5 of first 80 chars for cross-cycle dedup tracking."""
    return hashlib.md5(text[:80].lower().strip().encode()).hexdigest()
