"""
cortex/core/feature_flags.py — Feature Flags & Rollout Control (Issue #368).

Deterministic feature flag system for gradual rollout of Cortex capabilities.
Uses hash-based bucketing (no randomness) so the same (flag, owner_id) pair
always produces the same enabled/disabled decision.

Design decisions:
    - FeatureFlag is a simple dataclass — serialisable, config-friendly.
    - Deterministic bucketing via MD5 hash of (flag_name, owner_id) for
      consistent rollout percentages without external dependencies.
    - Safe defaults: unknown flags return False, disabled flags always return
      False regardless of rollout_pct.
    - Zero-overhead when disabled: FeatureFlagManager.is_enabled() short-circuits
      when feature_flags.enabled is False in config (returns True for all flags
      to avoid blocking any functionality).
    - No external dependencies: pure Python stdlib only.

Dependencies: cortex.config.CortexConfig (for config-driven overrides).
Dependents: cortex.api.plugin (pipeline gating).
"""
from __future__ import annotations

import hashlib
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from cortex.config import CortexConfig


# ---------------------------------------------------------------------------
# Data types
# ---------------------------------------------------------------------------

@dataclass
class FeatureFlag:
    """Definition of a single feature flag.

    Attributes:
        name: Unique identifier for the flag.
        enabled: Master toggle. When False, the feature is always off
                 regardless of rollout_pct.
        rollout_pct: Percentage of owners for which the feature is active
                     (0.0 = nobody, 1.0 = everyone). Uses deterministic
                     hash-based bucketing.
        description: Human-readable description of what the flag controls.
    """
    name: str
    enabled: bool = True
    rollout_pct: float = 1.0
    description: str = ""


# ---------------------------------------------------------------------------
# Default flags
# ---------------------------------------------------------------------------

DEFAULT_FLAGS: List[FeatureFlag] = [
    FeatureFlag(
        name="working_memory",
        enabled=True,
        rollout_pct=1.0,
        description="In-memory working memory cache for session context",
    ),
    FeatureFlag(
        name="task_mode_routing",
        enabled=True,
        rollout_pct=1.0,
        description="Task-mode driven retrieval policy routing",
    ),
    FeatureFlag(
        name="context_compiler",
        enabled=True,
        rollout_pct=1.0,
        description="Context compiler for assembling retrieval bundles",
    ),
    FeatureFlag(
        name="boot_loader",
        enabled=True,
        rollout_pct=1.0,
        description="Session boot loader for continuity context",
    ),
    FeatureFlag(
        name="re_retrieval",
        enabled=True,
        rollout_pct=1.0,
        description="Mid-turn supplemental re-retrieval",
    ),
    FeatureFlag(
        name="promotion_scoring",
        enabled=True,
        rollout_pct=1.0,
        description="Weighted promotion scoring for session → long-term",
    ),
    FeatureFlag(
        name="model_routing",
        enabled=True,
        rollout_pct=1.0,
        description="Three-tier model routing for LLM calls",
    ),
    FeatureFlag(
        name="sleep_pipeline",
        enabled=True,
        rollout_pct=1.0,
        description="Sleep pipeline for session consolidation",
    ),
    FeatureFlag(
        name="dream_cycle",
        enabled=True,
        rollout_pct=0.5,
        description="Background dream cycle consolidation (gradual rollout)",
    ),
    FeatureFlag(
        name="curiosity_seeds",
        enabled=True,
        rollout_pct=0.5,
        description="Curiosity seed injection during dream cycles (gradual rollout)",
    ),
]


# ---------------------------------------------------------------------------
# FeatureFlagManager
# ---------------------------------------------------------------------------

class FeatureFlagManager:
    """
    Deterministic feature flag manager for Cortex.

    Provides consistent, hash-based feature gating per (flag, owner_id) pair.
    When feature flags are disabled in config, all flags return True
    (everything is enabled — no blocking).

    Usage::

        flags = FeatureFlagManager(config=config)
        if flags.is_enabled("boot_loader", owner_id="user-123"):
            # run boot loader
            ...
    """

    def __init__(self, config: "CortexConfig") -> None:
        self._config = config
        self._flags: Dict[str, FeatureFlag] = {}
        self._ff_enabled = getattr(config, "feature_flags_enabled", True)

        # Load defaults
        for flag in DEFAULT_FLAGS:
            self._flags[flag.name] = FeatureFlag(
                name=flag.name,
                enabled=flag.enabled,
                rollout_pct=flag.rollout_pct,
                description=flag.description,
            )

        # Apply config overrides if present
        overrides = getattr(config, "feature_flag_overrides", None)
        if overrides and isinstance(overrides, dict):
            for flag_name, flag_config in overrides.items():
                if flag_name in self._flags and isinstance(flag_config, dict):
                    flag = self._flags[flag_name]
                    if "enabled" in flag_config:
                        flag.enabled = bool(flag_config["enabled"])
                    if "rollout_pct" in flag_config:
                        flag.rollout_pct = float(flag_config["rollout_pct"])

    def is_enabled(self, flag_name: str, owner_id: Optional[str] = None) -> bool:
        """Check if a feature flag is enabled for the given owner.

        Args:
            flag_name: Name of the feature flag.
            owner_id: Owner identifier for deterministic bucketing.
                      If None, uses the config's owner_id.

        Returns:
            True if the feature is enabled for this owner.
            False if the flag is unknown, disabled, or the owner is
            outside the rollout percentage.

        When ``feature_flags.enabled`` is False in config, always returns
        True (all features enabled — no gating).
        """
        # When feature flags system is disabled, everything is enabled
        if not self._ff_enabled:
            return True

        if flag_name not in self._flags:
            return False  # Safe default: unknown flags are off

        flag = self._flags[flag_name]

        if not flag.enabled:
            return False  # Disabled flag is always off

        if flag.rollout_pct >= 1.0:
            return True  # 100% rollout — always on

        if flag.rollout_pct <= 0.0:
            return False  # 0% rollout — always off

        # Deterministic hash-based bucketing
        resolved_owner = owner_id or getattr(self._config, "owner_id", "default")
        return _hash_bucket(flag_name, resolved_owner) < flag.rollout_pct

    def get_all(self) -> Dict[str, FeatureFlag]:
        """Return a copy of all registered feature flags."""
        return dict(self._flags)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _hash_bucket(flag_name: str, owner_id: str) -> float:
    """Deterministic hash-based bucketing.

    Returns a float in [0.0, 1.0) derived from the MD5 hash of
    (flag_name, owner_id). The same inputs always produce the same output.
    """
    key = f"{flag_name}:{owner_id}"
    digest = hashlib.md5(key.encode("utf-8")).hexdigest()
    # Use first 8 hex chars (32 bits) for sufficient granularity
    bucket = int(digest[:8], 16) / 0x100000000
    return bucket
