"""Pattern models — learned and seeded patterns with confidence, decay, matching.

A Pattern represents a known failure→fix mapping. Patterns are either
seeded (pre-loaded) or learned from successful AI-diagnosed fixes.
They support:
- Hierarchical signature matching
- Confidence tracking (increase on success, decrease on failure)
- Time-based decay (unused patterns fade)
- Effectiveness analysis
- Serialization for persistence
"""

import time
from dataclasses import dataclass, field
from typing import Any
from uuid import UUID, uuid4


@dataclass
class Pattern:
    """A learned or seeded pattern for failure recovery.

    Patterns match failure signatures at various granularity levels.
    Confidence adjusts based on usage outcomes: +0.05 on success,
    -0.10 on failure (asymmetric to prefer caution). Time-based
    decay ensures stale patterns don't dominate.
    """

    id: UUID = field(default_factory=uuid4)
    name: str = ""
    signature: str = ""
    category: str = ""
    description: str = ""
    fix_type: str = ""  # retry, modify_params, skip, alternative_tool, escalate
    fix_params: dict[str, Any] = field(default_factory=dict)
    confidence: float = 0.5
    is_seeded: bool = False
    uses: int = 0
    successes: int = 0
    failures: int = 0
    last_used: float = field(default_factory=time.time)
    created_at: float = field(default_factory=time.time)
    tags: list[str] = field(default_factory=list)

    # ── Usage tracking ─────────────────────────────────────────────

    def record_use(self, success: bool) -> None:
        """Record a usage of this pattern and adjust confidence.

        Asymmetric adjustment: +0.05 on success, -0.10 on failure.
        This makes patterns conservative — one failure undoes two successes.
        """
        self.uses += 1
        if success:
            self.successes += 1
            self.confidence = min(1.0, self.confidence + 0.05)
        else:
            self.failures += 1
            self.confidence = max(0.0, self.confidence - 0.1)
        self.last_used = time.time()

    def decay(self, days: float, rate: float = 0.001) -> None:
        """Apply time-based confidence decay.

        Seeded patterns decay at half rate.
        """
        effective_rate = rate * 0.5 if self.is_seeded else rate
        self.confidence = max(0.0, self.confidence - (days * effective_rate))

    # ── Matching ───────────────────────────────────────────────────

    def matches(self, signature: str) -> bool:
        """Check if this pattern matches a given signature.

        Matching levels (in order of priority):
        1. Exact match
        2. Prefix match (either direction)
        3. Component overlap (at least half of signature parts match)
        """
        if not self.signature or not signature:
            return False
        # Exact match
        if self.signature == signature:
            return True
        # Prefix match
        if signature.startswith(self.signature) or self.signature.startswith(
            signature
        ):
            return True
        # Component overlap
        sig_parts = set(self.signature.lower().split(":"))
        query_parts = set(signature.lower().split(":"))
        # Remove empty parts
        sig_parts.discard("")
        query_parts.discard("")
        if sig_parts and query_parts:
            overlap = len(sig_parts & query_parts)
            threshold = max(1, len(sig_parts) // 2)
            if overlap >= threshold:
                return True
        return False

    def match_score(self, signature: str) -> float:
        """Calculate a match score (0.0-1.0) for ranking multiple matches.

        Higher scores indicate closer matches.
        """
        if not self.signature or not signature:
            return 0.0
        if self.signature == signature:
            return 1.0
        # Prefix match: score based on overlap length
        common_prefix_len = 0
        for a, b in zip(self.signature, signature):
            if a == b:
                common_prefix_len += 1
            else:
                break
        if common_prefix_len > 0:
            max_len = max(len(self.signature), len(signature))
            prefix_score = common_prefix_len / max_len
            if prefix_score > 0.3:
                return prefix_score
        # Component overlap
        sig_parts = set(self.signature.lower().split(":"))
        query_parts = set(signature.lower().split(":"))
        sig_parts.discard("")
        query_parts.discard("")
        if sig_parts and query_parts:
            overlap = len(sig_parts & query_parts)
            total = len(sig_parts | query_parts)
            return overlap / total if total > 0 else 0.0
        return 0.0

    # ── Analysis properties ────────────────────────────────────────

    @property
    def success_rate(self) -> float:
        """Success rate as a fraction 0.0-1.0."""
        if self.uses == 0:
            return 0.0
        return self.successes / self.uses

    @property
    def failure_rate(self) -> float:
        """Failure rate as a fraction 0.0-1.0."""
        if self.uses == 0:
            return 0.0
        return self.failures / self.uses

    @property
    def is_effective(self) -> bool:
        """Pattern is effective if confidence >= 0.5 and success rate >= 0.5."""
        return self.confidence >= 0.5 and (self.uses == 0 or self.success_rate >= 0.5)

    @property
    def is_proven(self) -> bool:
        """Pattern is proven if used 3+ times with 70%+ success rate."""
        return self.uses >= 3 and self.success_rate >= 0.7

    @property
    def age_days(self) -> float:
        """Age of this pattern in days since creation."""
        return (time.time() - self.created_at) / 86400.0

    @property
    def staleness_days(self) -> float:
        """Days since this pattern was last used."""
        return (time.time() - self.last_used) / 86400.0

    @property
    def should_evict(self) -> bool:
        """Whether this pattern should be evicted (low confidence + not seeded)."""
        return not self.is_seeded and self.confidence < 0.2

    # ── Merge / similarity ─────────────────────────────────────────

    def merge_with(self, other: "Pattern") -> "Pattern":
        """Merge another pattern into this one, combining stats.

        Keeps the higher-confidence pattern's fix, merges usage stats.
        """
        if other.confidence > self.confidence:
            self.fix_type = other.fix_type
            self.fix_params = other.fix_params
            self.description = other.description
        self.uses += other.uses
        self.successes += other.successes
        self.failures += other.failures
        self.confidence = (self.confidence + other.confidence) / 2
        self.last_used = max(self.last_used, other.last_used)
        # Merge tags
        combined_tags = set(self.tags) | set(other.tags)
        self.tags = sorted(combined_tags)
        return self

    def similarity(self, other: "Pattern") -> float:
        """Compute similarity score between two patterns (0.0-1.0)."""
        score = 0.0
        # Same category
        if self.category == other.category:
            score += 0.3
        # Same fix type
        if self.fix_type == other.fix_type:
            score += 0.2
        # Signature similarity
        sig_score = self.match_score(other.signature)
        score += sig_score * 0.5
        return min(1.0, score)

    # ── Serialization ──────────────────────────────────────────────

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": str(self.id),
            "name": self.name,
            "signature": self.signature,
            "category": self.category,
            "description": self.description,
            "fix_type": self.fix_type,
            "fix_params": self.fix_params,
            "confidence": self.confidence,
            "is_seeded": self.is_seeded,
            "uses": self.uses,
            "successes": self.successes,
            "failures": self.failures,
            "last_used": self.last_used,
            "created_at": self.created_at,
            "tags": self.tags,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Pattern":
        return cls(
            id=UUID(data["id"]) if data.get("id") else uuid4(),
            name=data.get("name", ""),
            signature=data.get("signature", ""),
            category=data.get("category", ""),
            description=data.get("description", ""),
            fix_type=data.get("fix_type", ""),
            fix_params=data.get("fix_params", {}),
            confidence=data.get("confidence", 0.5),
            is_seeded=data.get("is_seeded", False),
            uses=data.get("uses", 0),
            successes=data.get("successes", 0),
            failures=data.get("failures", 0),
            last_used=data.get("last_used", time.time()),
            created_at=data.get("created_at", time.time()),
            tags=data.get("tags", []),
        )
