"""
Quality Scorer — Computes and updates memory quality scores.

PRD §9 — Quality Scoring Models:

§9.1 Memory Quality Score:
    quality_score = adoption_rate * 0.4 + success_rate * 0.4 + recency * 0.2

§9.2 Workflow Quality Score:
    workflow_score = success_rate * 0.5 + adoption_rate * 0.3 + recency * 0.2

§9.3 Noise Score:
    Incremented when memory is recalled+injected but never adopted.
    High noise → downrank.
"""

import time
import math
import logging
from openmemo.memory_feedback.feedback_store import (
    FeedbackStore, MemoryFeedbackRecord, WorkflowFeedbackRecord,
    MemoryQualityProfile
)

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

# Recency half-life: 7 days (in seconds)
_RECENCY_HALF_LIFE = 7 * 24 * 3600


def recency_score(timestamp: float, now: float = None,
                  half_life: float = _RECENCY_HALF_LIFE) -> float:
    """
    Exponential decay recency score [0.0, 1.0].
    Score = 1.0 at timestamp==now, decays to 0.5 after half_life seconds.
    """
    if now is None:
        now = time.time()
    age = max(0.0, now - timestamp)
    return math.exp(-math.log(2) * age / half_life)


class QualityScorer:
    """
    Computes quality scores for memories and workflows.

    Usage:
        scorer = QualityScorer(store)
        profile = scorer.update_memory_profile("mem_123")
        print(profile.quality_score)  # e.g. 0.78
    """

    def __init__(self, store: FeedbackStore):
        self.store = store

    # ── §9.1 Memory Quality Score ──────────────────────────────────────────────

    def score_memory(self, memory_id: str) -> float:
        """
        Compute and return quality_score for a memory.

        quality_score = adoption_rate * 0.4 + success_rate * 0.4 + recency * 0.2
        """
        profile = self.store.get_quality_profile(memory_id)
        if profile.usage_count == 0:
            return 0.0

        adoption_rate = profile.adoption_rate
        success_rate = profile.success_rate
        rec = recency_score(profile.last_updated)

        score = adoption_rate * 0.4 + success_rate * 0.4 + rec * 0.2
        return round(min(1.0, max(0.0, score)), 4)

    def update_memory_profile(self, memory_id: str,
                               recalled: bool = False,
                               injected: bool = False,
                               adopted: bool = False,
                               success: bool = False) -> MemoryQualityProfile:
        """
        Atomic update of quality profile based on a feedback event.

        Automatically increments noise_count when recalled+injected but not adopted.
        Recomputes quality_score.
        """
        profile = self.store.get_quality_profile(memory_id)

        if recalled:
            profile.usage_count += 1
        if injected:
            profile.injection_count += 1
        if adopted:
            profile.adoption_count += 1
        if success:
            profile.success_count += 1

        # §9.3 Noise: injected but not adopted
        if injected and not adopted:
            profile.noise_count += 1

        # §11.3 Archive candidate: high noise, low adoption
        if profile.noise_count >= 10 and profile.adoption_rate < 0.1:
            profile.archive_candidate = True

        profile.quality_score = self.score_memory_from_profile(profile)
        profile.last_updated = time.time()
        self.store.save_quality_profile(profile)

        logger.debug("quality: mem=%s score=%.3f noise=%d",
                     memory_id, profile.quality_score, profile.noise_count)
        return profile

    def score_memory_from_profile(self,
                                   profile: MemoryQualityProfile) -> float:
        """Compute score directly from profile (no DB lookup)."""
        if profile.usage_count == 0:
            return 0.0
        adoption = profile.adoption_rate
        success = profile.success_rate
        rec = recency_score(profile.last_updated)
        score = adoption * 0.4 + success * 0.4 + rec * 0.2
        return round(min(1.0, max(0.0, score)), 4)

    # ── §9.2 Workflow Quality Score ────────────────────────────────────────────

    def score_workflow(self, workflow_id: str) -> dict:
        """
        Compute quality scores for a workflow.

        workflow_score = success_rate * 0.5 + adoption_rate * 0.3 + recency * 0.2

        PRD §10.2 enhanced:
        workflow_score = success_rate * 0.4 + usage_count_norm * 0.2
                        + recency * 0.1 + adoption_rate * 0.2 + user_alignment * 0.1
        """
        records = self.store.get_workflow_feedback(workflow_id, limit=200)
        if not records:
            return {"workflow_id": workflow_id, "score": 0.0,
                    "adoption_rate": 0.0, "success_rate": 0.0,
                    "total": 0}

        total = len(records)
        adopted = sum(1 for r in records if r.adopted)
        successes = sum(1 for r in records if r.success)
        latest_ts = max(r.timestamp for r in records)

        adoption_rate = adopted / total
        success_rate = successes / adopted if adopted else 0.0
        rec = recency_score(latest_ts)

        # §9.2 formula
        score = success_rate * 0.5 + adoption_rate * 0.3 + rec * 0.2
        score = round(min(1.0, max(0.0, score)), 4)

        # §10.2 enhanced score
        usage_norm = min(1.0, total / 50.0)  # normalise at 50 uses
        enhanced_score = (
            success_rate * 0.4 + usage_norm * 0.2 +
            rec * 0.1 + adoption_rate * 0.2
        )
        enhanced_score = round(min(1.0, max(0.0, enhanced_score)), 4)

        return {
            "workflow_id": workflow_id,
            "score": score,
            "enhanced_score": enhanced_score,
            "adoption_rate": round(adoption_rate, 4),
            "success_rate": round(success_rate, 4),
            "recency": round(rec, 4),
            "total": total,
        }

    # ── Bulk operations ────────────────────────────────────────────────────────

    def refresh_all_profiles(self) -> int:
        """Recompute quality_score for all profiles in DB. Returns count."""
        profiles = self.store.list_profiles(min_quality=0.0, limit=10000)
        count = 0
        for profile in profiles:
            profile.quality_score = self.score_memory_from_profile(profile)
            self.store.save_quality_profile(profile)
            count += 1
        return count
