"""
Context Summary — Compresses session conversation into structured summary.

PRD §7 — Summary Generation:
    Trigger: on_n_messages (every N), on_task_complete, on_context_overflow
    Output:  { goal, progress, decisions, issues, summary }

Uses rule-based extraction (MVP). LLM-based extraction is supported
when an OpenAI-compatible client is injected.
"""

import re
import time
import logging
from typing import Optional, List
from openmemo.context_memory.context_store import (
    ContextStore, SessionNode, RawMessage
)

logger = logging.getLogger("openmemo.context_memory")

# ── Structured summary fields ─────────────────────────────────────────────────

_GOAL_SIGNALS = [
    r"(?:I\s+)?(?:want|need|need to|would like to|trying to|help me)\s+(.+)",
    r"(?:please\s+)?(?:deploy|build|create|implement|fix|write|generate|make)\s+(.+)",
    r"(?:task|goal|objective)[:\s]+(.+)",
]

_DECISION_SIGNALS = [
    r"(?:I\s+)?(?:decided?|chose?|going with|will use|using|use)\s+(.+)",
    r"(?:let['\u2019]?s|we['\u2019]ll|we will)\s+(?:use|go with|deploy)\s+(.+)",
]

_ISSUE_SIGNALS = [
    r"(?:error|failed?|issue|problem|bug|broken)[:\s]+(.+)",
    r"(?:doesn['\u2019]?t work|not working|failing)[:\s]?(.+)?",
]

_PROGRESS_SIGNALS = [
    r"(?:done|completed?|finished?|built?|deployed?|running|started?)[:\s]+(.+)",
    r"(?:successfully|now)\s+(.+)",
]


class ContextSummary:
    """
    Generates structured session summaries.

    Usage:
        summarizer = ContextSummary(store)
        session = summarizer.summarize("session_1")
        print(session.summary)
    """

    def __init__(self, store: ContextStore,
                 summarize_every_n: int = 5,
                 llm_client=None):
        self.store = store
        self.summarize_every_n = summarize_every_n
        self._llm = llm_client  # optional: OpenAI-compatible client

    # ── Public API ─────────────────────────────────────────────────────────────

    def should_summarize(self, session_id: str) -> bool:
        """True when message count hits a summarize threshold."""
        session = self.store.get_session(session_id)
        if not session:
            return False
        return session.message_count > 0 and (
            session.message_count % self.summarize_every_n == 0
        )

    def summarize(self, session_id: str) -> SessionNode:
        """
        (Re-)generate summary for a session.

        Tries LLM extraction first (if configured), falls back to rule-based.
        Updates the session in the store and returns the updated SessionNode.
        """
        session = self.store.get_session(session_id)
        if not session:
            return SessionNode(session_id=session_id)

        messages = self.store.get_raw_messages(session_id, limit=100)
        if not messages:
            return session

        if self._llm:
            updated = self._llm_summarize(session, messages)
        else:
            updated = self._rule_summarize(session, messages)

        self.store.save_session(updated)
        logger.debug("summarize session=%s msgs=%d", session_id, len(messages))
        return updated

    def summarize_on_complete(self, session_id: str,
                               task: str = "", result: str = "") -> SessionNode:
        """Called at task completion (PRD §7.1 trigger)."""
        session = self.store.get_or_create_session(session_id)
        if result:
            session.progress = result[:500]
        if task and not session.goal:
            session.goal = task[:200]
        # Persist progress/goal BEFORE summarize reads session from store
        self.store.save_session(session)
        return self.summarize(session_id)

    def format_for_prompt(self, session_id: str) -> str:
        """
        Return CONTEXT SUMMARY block for prompt injection (PRD §11.2).

        Returns empty string if no meaningful summary exists.
        """
        session = self.store.get_session(session_id)
        if not session or not any([session.goal, session.progress,
                                    session.decisions, session.summary]):
            return ""

        lines = ["CONTEXT SUMMARY"]
        if session.goal:
            lines.append(f"  Goal: {session.goal}")
        if session.progress:
            lines.append(f"  Progress: {session.progress}")
        if session.decisions:
            lines.append(f"  Decision: {session.decisions}")
        if session.issues:
            lines.append(f"  Issues: {session.issues}")
        if session.summary and session.summary not in (
            session.goal, session.progress
        ):
            lines.append(f"  Summary: {session.summary}")

        return "\n".join(lines)

    # ── Rule-based extraction ──────────────────────────────────────────────────

    def _rule_summarize(self, session: SessionNode,
                         messages: List[RawMessage]) -> SessionNode:
        all_text = " ".join(m.content for m in messages)
        sentences = re.split(r"[.!?\n]", all_text)

        goals, decisions, issues, progress = [], [], [], []

        for sent in sentences:
            sent = sent.strip()
            if not sent or len(sent) < 5:
                continue
            if self._match_any(sent, _GOAL_SIGNALS):
                goals.append(sent[:150])
            elif self._match_any(sent, _DECISION_SIGNALS):
                decisions.append(sent[:150])
            elif self._match_any(sent, _ISSUE_SIGNALS):
                issues.append(sent[:150])
            elif self._match_any(sent, _PROGRESS_SIGNALS):
                progress.append(sent[:150])

        session.goal = goals[0] if goals else session.goal
        session.decisions = "; ".join(decisions[:3]) if decisions else session.decisions
        session.issues = "; ".join(issues[:2]) if issues else session.issues
        session.progress = progress[-1] if progress else session.progress

        parts = []
        if session.goal:
            parts.append(f"Goal: {session.goal}")
        if session.progress:
            parts.append(f"Progress: {session.progress}")
        if session.decisions:
            parts.append(f"Decisions: {session.decisions}")
        session.summary = ". ".join(parts)[:800]
        session.last_updated = time.time()
        return session

    def _match_any(self, text: str, patterns: List[str]) -> bool:
        tl = text.lower()
        return any(re.search(p, tl, re.IGNORECASE) for p in patterns)

    # ── LLM extraction (optional) ──────────────────────────────────────────────

    def _llm_summarize(self, session: SessionNode,
                        messages: List[RawMessage]) -> SessionNode:
        """Lightweight LLM-based structured summary (PRD §7.4)."""
        try:
            transcript = "\n".join(
                f"[{m.role}] {m.content[:300]}" for m in messages[-20:]
            )
            prompt = (
                "Summarize this session concisely in this JSON format:\n"
                '{"goal":"...","progress":"...","decisions":"...","issues":"..."}\n\n'
                f"Session:\n{transcript}\n\nJSON:"
            )
            resp = self._llm.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": prompt}],
                max_tokens=300,
                temperature=0.2,
            )
            import json as _json
            raw = resp.choices[0].message.content.strip()
            # Extract JSON from response
            match = re.search(r'\{.*\}', raw, re.DOTALL)
            if match:
                data = _json.loads(match.group())
                session.goal = data.get("goal", session.goal)[:300]
                session.progress = data.get("progress", session.progress)[:300]
                session.decisions = data.get("decisions", session.decisions)[:300]
                session.issues = data.get("issues", session.issues)[:300]
                session.summary = f"Goal: {session.goal}. Progress: {session.progress}"
                session.last_updated = time.time()
        except Exception as e:
            logger.warning("llm_summarize failed, falling back to rules: %s", e)
            return self._rule_summarize(session, messages)
        return session
