"""
cortex/storage/queries/activation_queries.py — memory_activation table CRUD mixin.

Provides create_activation() and read-back helpers for the memory_activation
table (introduced in schema v8 by #355).

Used by: ActivationRouter (via SQLiteAdapter), retrieval pipeline (session_boot).
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass
from typing import List, Optional

logger = logging.getLogger(__name__)


@dataclass
class MemoryActivation:
    """Row from the memory_activation table.

    Attributes:
        id:               Hex UUID PK.
        owner_id:         Multi-tenancy scope key.
        claim_id:         FK → semantic_claim.id
        scope:            "private" | "user" | "session"
        trigger_pattern:  Comma-separated trigger tags, or None
        suppressors_json: JSON array of suppressor tag strings
        priority:         0-10 (higher = surface first)
        decay_factor:     0.0-1.0
        created_at:       ISO 8601 UTC
        updated_at:       ISO 8601 UTC
    """
    id: str
    owner_id: str
    claim_id: str
    scope: str
    trigger_pattern: Optional[str]
    suppressors_json: str
    priority: int
    decay_factor: float
    created_at: str
    updated_at: str

    @property
    def suppressors(self) -> List[str]:
        """Deserialized suppressors list."""
        try:
            return json.loads(self.suppressors_json or "[]")
        except (json.JSONDecodeError, TypeError):
            return []

    @property
    def triggers(self) -> List[str]:
        """Deserialized trigger tags list."""
        if not self.trigger_pattern:
            return []
        return [t.strip() for t in self.trigger_pattern.split(",") if t.strip()]


def _row_to_activation(row) -> MemoryActivation:
    """Convert an aiosqlite Row to a MemoryActivation dataclass."""
    return MemoryActivation(
        id=row["id"],
        owner_id=row["owner_id"],
        claim_id=row["claim_id"],
        scope=row["scope"],
        trigger_pattern=row["trigger_pattern"],
        suppressors_json=row["suppressors_json"] or "[]",
        priority=int(row["priority"] or 0),
        decay_factor=float(row["decay_factor"] or 1.0),
        created_at=row["created_at"],
        updated_at=row["updated_at"],
    )


class ActivationQueryMixin:
    """Mixin providing memory_activation CRUD operations.

    Composable into SQLiteAdapter via multiple inheritance.
    Requires: self._write_lock, self._get_conn() (from SQLiteAdapter).
    """

    async def create_activation(
        self,
        claim_id: str,
        owner_id: str = "default",
        scope: str = "user",
        trigger_pattern: Optional[str] = None,
        suppressors_json: str = "[]",
        priority: int = 0,
        decay_factor: float = 1.0,
    ) -> MemoryActivation:
        """Insert a memory_activation row and return the created record.

        Owner-scope is always propagated from the caller; never defaulted away
        silently. The ``owner_id`` parameter is required (default='default' is
        only for backward compatibility with direct-call tests).

        Args:
            claim_id:         FK → semantic_claim.id (required).
            owner_id:         Multi-tenancy scope key.
            scope:            Visibility scope — "private" | "user" | "session".
            trigger_pattern:  Comma-separated trigger tags or None.
            suppressors_json: JSON array of suppressor tags.
            priority:         0-10 (higher = surface first).
            decay_factor:     0.0-1.0 (1.0 = never decays).

        Returns:
            The newly inserted MemoryActivation row.

        Raises:
            Exception: Propagates any storage-level error to the caller.
                       The caller (ActivationRouter.write) handles these
                       as non-fatal warnings.
        """
        async with self._write_lock:
            conn = await self._get_conn()
            async with conn.execute(
                """
                INSERT INTO memory_activation
                    (owner_id, claim_id, scope, trigger_pattern,
                     suppressors_json, priority, decay_factor)
                VALUES (?, ?, ?, ?, ?, ?, ?)
                RETURNING *
                """,
                (
                    owner_id,
                    claim_id,
                    scope,
                    trigger_pattern,
                    suppressors_json,
                    priority,
                    decay_factor,
                ),
            ) as cur:
                row = await cur.fetchone()
            await conn.commit()

        return _row_to_activation(row)

    async def get_activation_by_claim(
        self,
        claim_id: str,
        owner_id: str = "default",
    ) -> Optional[MemoryActivation]:
        """Return the activation record for a given claim, or None.

        Args:
            claim_id:  The claim whose activation record to fetch.
            owner_id:  Multi-tenancy scope key (enforced on query).

        Returns:
            MemoryActivation if found, None otherwise.
        """
        conn = await self._get_conn()
        async with conn.execute(
            """
            SELECT * FROM memory_activation
            WHERE claim_id = ? AND owner_id = ?
            ORDER BY created_at DESC
            LIMIT 1
            """,
            (claim_id, owner_id),
        ) as cur:
            row = await cur.fetchone()
        if row is None:
            return None
        return _row_to_activation(row)

    async def get_session_boot_activations(
        self,
        owner_id: str = "default",
        min_priority: int = 6,
        limit: int = 50,
    ) -> List[MemoryActivation]:
        """Return activation records that should be loaded at session boot.

        Filters to rows where trigger_pattern contains "session_boot" AND
        priority >= min_priority. Owner-scope is always enforced.

        Args:
            owner_id:      Multi-tenancy scope key.
            min_priority:  Minimum priority threshold (default 6 matches
                           _SESSION_BOOT_PRIORITY_THRESHOLD in activation_router).
            limit:         Max rows to return (ordered by priority DESC).

        Returns:
            List of MemoryActivation rows, highest priority first.
        """
        conn = await self._get_conn()
        async with conn.execute(
            """
            SELECT * FROM memory_activation
            WHERE owner_id = ?
              AND trigger_pattern LIKE '%session_boot%'
              AND priority >= ?
            ORDER BY priority DESC, created_at DESC
            LIMIT ?
            """,
            (owner_id, min_priority, limit),
        ) as cur:
            rows = await cur.fetchall()
        return [_row_to_activation(r) for r in rows]

    async def get_activations_by_scope(
        self,
        owner_id: str,
        scope: str,
        limit: int = 100,
    ) -> List[MemoryActivation]:
        """Return all activation records for an owner with the given scope.

        Useful for auditing which memories are private vs. user-scoped.

        Args:
            owner_id:  Multi-tenancy scope key.
            scope:     "private" | "user" | "session"
            limit:     Max rows returned.

        Returns:
            List of MemoryActivation rows ordered by priority DESC.
        """
        conn = await self._get_conn()
        async with conn.execute(
            """
            SELECT * FROM memory_activation
            WHERE owner_id = ? AND scope = ?
            ORDER BY priority DESC, created_at DESC
            LIMIT ?
            """,
            (owner_id, scope, limit),
        ) as cur:
            rows = await cur.fetchall()
        return [_row_to_activation(r) for r in rows]
