"""Pattern tracking for GreyMatter.

Patterns represent learned behaviors with confidence escalation:
- low: 0-2 confirmations
- medium: 3-6 confirmations
- high: 7+ confirmations
"""
from __future__ import annotations

import logging
from typing import Any, Optional, TYPE_CHECKING

from .db import Database
from .ids import generate_pattern_id

if TYPE_CHECKING:
    from .cache import PatternCache
    from .lineage import LineageTracker

logger = logging.getLogger("greymatter.patterns")

CONFIDENCE_THRESHOLDS = {"low": 0, "medium": 3, "high": 7}


class PatternManager:
    """Manages pattern lifecycle: creation, evidence, confidence escalation.

    Optionally backed by a ``PatternCache`` for frequently accessed queries.
    When no cache is provided (or caching is disabled), all reads go
    directly to the database — the API is identical either way.
    """

    def __init__(
        self,
        db: Database,
        lineage: "LineageTracker | None" = None,
        cache: "PatternCache | None" = None,
    ):
        self.db = db
        self._lineage = lineage
        self._cache = cache

    def create(self, type: str, domain: str, title: str, observation: str) -> dict[str, Any]:
        """Create a new pattern with initial low confidence.

        Invalidates aggregate caches (search, list, count) since a new
        pattern affects those results.

        Args:
            type: Pattern type (e.g. 'process', 'style').
            domain: Domain the pattern belongs to (e.g. 'testing', 'coding').
            title: Short descriptive title.
            observation: The observed behavior text.

        Returns:
            The created pattern dict.
        """
        pattern_id = generate_pattern_id()
        self.db.execute(
            "INSERT INTO patterns (id, type, domain, title, observation) VALUES (?, ?, ?, ?, ?)",
            (pattern_id, type, domain, title, observation),
        )
        if self._cache:
            self._cache.invalidate()
        return self.get(pattern_id)

    def create_from_observations(
        self,
        type: str,
        domain: str,
        title: str,
        observation: str,
        source_observation_ids: list[str] | None = None,
        session_id: str = "",
    ) -> dict[str, Any]:
        """Create a pattern and record lineage from source observations.

        Args:
            type: Pattern type.
            domain: Pattern domain.
            title: Pattern title.
            observation: Pattern observation text.
            source_observation_ids: IDs of observations that led to this pattern.
            session_id: Session where promotion occurred.

        Returns:
            The created pattern dict.
        """
        pattern = self.create(type=type, domain=domain, title=title, observation=observation)

        if self._lineage:
            for obs_id in (source_observation_ids or []):
                self._lineage.record_event(
                    source_id=obs_id,
                    source_type="observation",
                    target_id=pattern["id"],
                    target_type="pattern",
                    event_type="promoted_to_pattern",
                    session_id=session_id,
                )
                # Update the observation's pattern_id foreign key
                self.db.execute(
                    "UPDATE observations SET pattern_id = ? WHERE id = ?",
                    (pattern["id"], obs_id),
                )

        return pattern

    def get(self, pattern_id: str) -> dict[str, Any] | None:
        """Retrieve a pattern by ID.

        Checks the cache first; on miss, queries the database and populates
        the cache for subsequent reads.

        Args:
            pattern_id: The pattern's unique identifier.

        Returns:
            The pattern dict, or None if not found.
        """
        if self._cache:
            cached = self._cache.get_pattern(pattern_id)
            if cached is not None:
                return cached

        result = self.db.execute_one("SELECT * FROM patterns WHERE id = ?", (pattern_id,))

        if result is not None and self._cache:
            self._cache.set_pattern(pattern_id, result)

        return result

    def add_evidence(self, pattern_id: str, session_id: str,
                     work_item_id: str | None = None, outcome: str = "confirmed") -> None:
        """Record a new evidence observation for a pattern.

        Increments the application count and recalculates confidence based
        on the number of confirmed evidence records.  Invalidates the cache
        since confidence may change.

        Args:
            pattern_id: The pattern to add evidence for.
            session_id: Session where the evidence was observed.
            work_item_id: Optional associated work item.
            outcome: Evidence outcome, typically 'confirmed'.
        """
        self.db.execute(
            "INSERT INTO pattern_evidence (pattern_id, session_id, work_item_id, outcome) VALUES (?, ?, ?, ?)",
            (pattern_id, session_id, work_item_id, outcome),
        )
        self.db.execute(
            "UPDATE patterns SET applications = applications + 1, last_applied_at = datetime('now') WHERE id = ?",
            (pattern_id,),
        )
        self._update_confidence(pattern_id)
        if self._cache:
            self._cache.invalidate(pattern_id)

        if self._lineage and outcome == "confirmed":
            pattern = self.get(pattern_id)
            self._lineage.record_event(
                source_id=pattern_id,
                source_type="pattern",
                target_id=pattern_id,
                target_type="pattern",
                event_type="pattern_confirmed",
                session_id=session_id,
                metadata={
                    "confirmations": pattern["confirmations"] if pattern else 0,
                    "confidence": pattern["confidence"] if pattern else "low",
                    "work_item_id": work_item_id or "",
                },
            )

    def _update_confidence(self, pattern_id: str) -> None:
        rows = self.db.execute(
            "SELECT COUNT(*) as cnt FROM pattern_evidence WHERE pattern_id = ? AND outcome = 'confirmed'",
            (pattern_id,),
        )
        count = rows[0]["cnt"] if rows else 0

        new_confidence = "low"
        if count >= CONFIDENCE_THRESHOLDS["high"]:
            new_confidence = "high"
        elif count >= CONFIDENCE_THRESHOLDS["medium"]:
            new_confidence = "medium"

        self.db.execute(
            "UPDATE patterns SET confidence = ?, confirmations = ? WHERE id = ?",
            (new_confidence, count, pattern_id),
        )

    def search(self, domain: str | None = None, type: str | None = None,
               min_confidence: str = "low") -> list[dict[str, Any]]:
        """Search patterns with optional domain, type, and confidence filters.

        Args:
            domain: Filter to patterns in this domain.
            type: Filter to patterns of this type.
            min_confidence: Minimum confidence level ('low', 'medium', 'high').

        Returns:
            List of matching pattern dicts.
        """
        query = "SELECT * FROM patterns WHERE 1=1"
        params: list = []
        if domain:
            query += " AND domain = ?"
            params.append(domain)
        if type:
            query += " AND type = ?"
            params.append(type)

        results = self.db.execute(query, tuple(params))
        confidence_order = {"low": 0, "medium": 1, "high": 2}
        min_val = confidence_order.get(min_confidence, 0)
        return [p for p in results if confidence_order.get(p["confidence"], 0) >= min_val]

    def get_for_session_load(self, max_count: int = 20) -> list[dict[str, Any]]:
        """Return medium+ confidence patterns for session context injection.

        This is one of the most frequently called methods (every session
        start), so it benefits significantly from caching.

        Patterns are ordered by confidence (high first) then by application
        count, limited to max_count results.

        Args:
            max_count: Maximum number of patterns to return.

        Returns:
            List of pattern dicts with medium or high confidence.
        """
        if self._cache:
            cached = self._cache.get_session_load(max_count)
            if cached is not None:
                return cached

        results = self.db.execute(
            """SELECT * FROM patterns WHERE confidence IN ('medium', 'high')
               ORDER BY CASE confidence WHEN 'high' THEN 0 ELSE 1 END, applications DESC
               LIMIT ?""",
            (max_count,),
        )

        if self._cache:
            self._cache.set_session_load(max_count, results)

        return results

    def list_all(self) -> list[dict[str, Any]]:
        """Return all patterns ordered by creation date (newest first)."""
        return self.db.execute("SELECT * FROM patterns ORDER BY created_at DESC")

    def list_summary(self, limit: int = 25, confidence: str | None = None) -> dict[str, Any]:
        """List patterns as compact summaries without observation bodies.

        Results are cached by (limit, confidence) parameters.
        """
        if self._cache:
            cached = self._cache.get_list_summary(limit, confidence)
            if cached is not None:
                return cached

        where = "WHERE 1=1"
        params: list = []
        if confidence:
            where += " AND confidence = ?"
            params.append(confidence)
        count_row = self.db.execute_one(
            f"SELECT COUNT(*) as total FROM patterns {where}", tuple(params)
        )
        total = count_row["total"] if count_row else 0
        results = self.db.execute(
            f"""SELECT id, type, domain, title, confidence, confirmations, created_at
                FROM patterns {where}
                ORDER BY created_at DESC LIMIT ?""",
            tuple(params + [limit]),
        )
        result = {"total": total, "returned": len(results), "patterns": results}

        if self._cache:
            self._cache.set_list_summary(limit, confidence, result)

        return result

    def search_summary(
        self,
        domain: str | None = None,
        type: str | None = None,
        min_confidence: str = "low",
        limit: int = 25,
    ) -> dict[str, Any]:
        """Search patterns returning compact summaries."""
        where = "WHERE 1=1"
        params: list = []
        if domain:
            where += " AND domain = ?"
            params.append(domain)
        if type:
            where += " AND type = ?"
            params.append(type)

        all_results = self.db.execute(
            f"SELECT id, type, domain, title, confidence, confirmations, created_at FROM patterns {where}",
            tuple(params),
        )
        confidence_order = {"low": 0, "medium": 1, "high": 2}
        min_val = confidence_order.get(min_confidence, 0)
        filtered = [p for p in all_results if confidence_order.get(p["confidence"], 0) >= min_val]
        return {
            "total": len(filtered),
            "returned": min(limit, len(filtered)),
            "patterns": filtered[:limit],
        }


    def count(self, confidence: str | None = None) -> int:
        """Count patterns, optionally filtered by confidence.

        Cached because it is called frequently for budget-level responses.
        """
        if self._cache:
            cached = self._cache.get_count(confidence)
            if cached is not None:
                return cached

        where = "WHERE 1=1"
        params: list = []
        if confidence:
            where += " AND confidence = ?"
            params.append(confidence)
        row = self.db.execute_one(
            f"SELECT COUNT(*) as total FROM patterns {where}", tuple(params)
        )
        result = row["total"] if row else 0

        if self._cache:
            self._cache.set_count(confidence, result)

        return result

    def delete(self, pattern_id: str) -> bool:
        """Delete a pattern and all its evidence records.

        Args:
            pattern_id: The pattern to delete.

        Returns:
            True if the pattern was deleted, False if not found.
        """
        if self.get(pattern_id) is None:
            return False
        self.db.execute("DELETE FROM pattern_evidence WHERE pattern_id = ?", (pattern_id,))
        self.db.execute("DELETE FROM patterns WHERE id = ?", (pattern_id,))
        if self._cache:
            self._cache.invalidate(pattern_id)
        return True
