"""cortex/storage/queries/session_queries.py — Session lifecycle storage mixin.

Extracted from claim_queries.py to keep session operations in their own module.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional

from cortex.storage._helpers import _now, _row_to_claim, _row_to_session
from cortex.types import SemanticClaim


class SessionQueryMixin:
    """Mixin providing session CRUD and lifecycle methods."""

    async def get_session_claims(self, session_id: str) -> List[SemanticClaim]:
        """Return all claims belonging to a specific session (session-tier working memory)."""
        conn = await self._get_conn()
        async with conn.execute(
            "SELECT * FROM semantic_claim WHERE source_session = ?",
            (session_id,),
        ) as cur:
            rows = await cur.fetchall()
        return [_row_to_claim(r) for r in rows]

    async def close_session(self, session_id: str, **kwargs) -> None:
        """Mark a session as closed and record ended_at timestamp."""
        async with self._write_lock:
            conn = await self._get_conn()
            now = _now()
            await conn.execute(
                """
                UPDATE session_record
                SET status = 'closed', ended_at = ?
                WHERE session_id = ?
                """,
                (now, session_id),
            )
            await conn.commit()

    async def list_sessions(
        self,
        owner_id: str = "default",
        limit: int = 50,
        offset: int = 0,
    ) -> List[Dict[str, Any]]:
        """Browse sessions with claim counts."""
        conn = await self._get_conn()
        async with conn.execute(
            """
            SELECT s.*,
                   COUNT(c.id) AS claims_created_count
            FROM session_record s
            LEFT JOIN semantic_claim c ON c.source_session = s.id
            WHERE s.owner_id = ?
            GROUP BY s.id
            ORDER BY s.started_at DESC
            LIMIT ? OFFSET ?
            """,
            (owner_id, limit, offset),
        ) as cur:
            rows = await cur.fetchall()
        return [dict(r) for r in rows]

    async def count_sessions(self, owner_id: str = "default") -> int:
        conn = await self._get_conn()
        async with conn.execute(
            "SELECT COUNT(*) FROM session_record WHERE owner_id = ?", (owner_id,)
        ) as cur:
            row = await cur.fetchone()
        return row[0] if row else 0

    async def get_session_episode_count(self, session_id: str, owner_id: Optional[str] = None) -> int:
        conn = await self._get_conn()
        if owner_id is None:
            async with conn.execute(
                "SELECT COUNT(*) FROM episode_event WHERE session_id = ?", (session_id,)
            ) as cur:
                row = await cur.fetchone()
        else:
            async with conn.execute(
                """
                SELECT COUNT(*)
                FROM episode_event e
                JOIN session_record s ON s.id = e.session_id
                WHERE s.session_id = ? AND s.owner_id = ?
                """,
                (session_id, owner_id),
            ) as cur:
                row = await cur.fetchone()
        return row[0] if row else 0

    # ------------------------------------------------------------------ #
    # Session operations
    # ------------------------------------------------------------------ #

    async def create_session(
        self, session_id: str, owner_id: str = "default"
    ):
        from cortex.storage._helpers import _now, _row_to_session
        async with self._write_lock:
            conn = await self._get_conn()
            now = _now()
            async with conn.execute(
                """
                INSERT INTO session_record
                    (owner_id, session_id, started_at, status, created_at)
                VALUES (?, ?, ?, 'awake', ?)
                RETURNING *
                """,
                (owner_id, session_id, now, now),
            ) as cur:
                row = await cur.fetchone()
            await conn.commit()
            return _row_to_session(row)

    async def get_session_by_sid(
        self, session_id: str, owner_id: str = "default"
    ):
        """Look up a session_record by its application-level session_id."""
        from cortex.storage._helpers import _row_to_session
        conn = await self._get_conn()
        async with conn.execute(
            "SELECT * FROM session_record WHERE session_id = ? AND owner_id = ?",
            (session_id, owner_id),
        ) as cur:
            row = await cur.fetchone()
        return _row_to_session(row) if row else None

    async def update_session(self, session_id: str, **kwargs):
        """Patch columns on a session identified by session_id (not PK)."""
        from cortex.storage._helpers import _row_to_session
        if not kwargs:
            raise ValueError("update_session requires at least one keyword argument")

        owner_id = kwargs.pop("owner_id", None)

        _allowed = {
            "status", "ended_at", "duration_seconds", "fatigue_score",
            "compaction_count", "turn_count", "summary", "sleep_report",
            "dream_report", "memories_created", "memories_promoted",
            "memories_pruned",
        }
        updates = {k: v for k, v in kwargs.items() if k in _allowed}
        if not updates:
            raise ValueError(f"No valid columns to update. Allowed: {_allowed}")

        async with self._write_lock:
            conn = await self._get_conn()
            set_clause = ", ".join(f"{col} = ?" for col in updates)

            if owner_id is None:
                values = list(updates.values()) + [session_id]
                await conn.execute(
                    f"UPDATE session_record SET {set_clause} WHERE session_id = ?",
                    values,
                )
                async with conn.execute(
                    "SELECT * FROM session_record WHERE session_id = ? ORDER BY created_at DESC LIMIT 1",
                    (session_id,),
                ) as cur:
                    row = await cur.fetchone()
            else:
                values = list(updates.values()) + [session_id, owner_id]
                await conn.execute(
                    f"UPDATE session_record SET {set_clause} WHERE session_id = ? AND owner_id = ?",
                    values,
                )
                async with conn.execute(
                    "SELECT * FROM session_record WHERE session_id = ? AND owner_id = ? ORDER BY created_at DESC LIMIT 1",
                    (session_id, owner_id),
                ) as cur:
                    row = await cur.fetchone()

            await conn.commit()
            assert row is not None
            return _row_to_session(row)

    async def increment_session_counters(
        self,
        session_id: str,
        owner_id: str = "default",
        turn_delta: int = 1,
        memories_created_delta: int = 0,
    ):
        """Atomically increment turn_count/memories_created for one owner-scoped session."""
        from cortex.storage._helpers import _row_to_session

        turns = max(0, int(turn_delta))
        memories = max(0, int(memories_created_delta))

        async with self._write_lock:
            conn = await self._get_conn()
            await conn.execute(
                """
                UPDATE session_record
                SET
                    turn_count = COALESCE(turn_count, 0) + ?,
                    memories_created = COALESCE(memories_created, 0) + ?
                WHERE session_id = ? AND owner_id = ?
                """,
                (turns, memories, session_id, owner_id),
            )
            await conn.commit()

            async with conn.execute(
                """
                SELECT * FROM session_record
                WHERE session_id = ? AND owner_id = ?
                ORDER BY created_at DESC
                LIMIT 1
                """,
                (session_id, owner_id),
            ) as cur:
                row = await cur.fetchone()

        return _row_to_session(row) if row else None

    async def get_active_session(
        self, owner_id: str = "default"
    ):
        from cortex.storage._helpers import _row_to_session
        conn = await self._get_conn()
        async with conn.execute(
            """
            SELECT * FROM session_record
            WHERE owner_id = ? AND status = 'awake'
            ORDER BY started_at DESC
            LIMIT 1
            """,
            (owner_id,),
        ) as cur:
            row = await cur.fetchone()
        return _row_to_session(row) if row else None

    async def get_active_session_by_id(
        self, session_id: str, owner_id: str = "default"
    ):
        """Look up an active (awake) session by its application-level session_id.

        Returns the SessionRecord if found and status is 'awake', else None.
        Used by CircadianEngine._get_session for fatigue tracking.
        """
        from cortex.storage._helpers import _row_to_session
        conn = await self._get_conn()
        async with conn.execute(
            """
            SELECT * FROM session_record
            WHERE session_id = ? AND owner_id = ? AND status = 'awake'
            LIMIT 1
            """,
            (session_id, owner_id),
        ) as cur:
            row = await cur.fetchone()
        return _row_to_session(row) if row else None

    async def get_orphaned_sessions(
        self, idle_seconds: int
    ):
        from cortex.storage._helpers import _row_to_session
        from datetime import datetime, timezone
        conn = await self._get_conn()
        cutoff = datetime.now(timezone.utc).timestamp() - idle_seconds
        async with conn.execute(
            """
            SELECT * FROM session_record
            WHERE status = 'awake'
              AND strftime('%s', started_at) < ?
            """,
            (str(int(cutoff)),),
        ) as cur:
            rows = await cur.fetchall()
        return [_row_to_session(r) for r in rows]

    async def get_interrupted_sessions(
        self, owner_id=None
    ):
        """Return sessions stuck in 'sleeping' or 'dreaming' with no ended_at."""
        from cortex.storage._helpers import _row_to_session
        conn = await self._get_conn()
        if owner_id is not None:
            async with conn.execute(
                """
                SELECT * FROM session_record
                WHERE status IN ('sleeping', 'dreaming')
                  AND ended_at IS NULL
                  AND owner_id = ?
                """,
                (owner_id,),
            ) as cur:
                rows = await cur.fetchall()
        else:
            async with conn.execute(
                """
                SELECT * FROM session_record
                WHERE status IN ('sleeping', 'dreaming')
                  AND ended_at IS NULL
                """
            ) as cur:
                rows = await cur.fetchall()
        return [_row_to_session(r) for r in rows]

    # ------------------------------------------------------------------ #
    # handoff_packet CRUD (v8 schema — Issue #360)
    # ------------------------------------------------------------------ #

    async def create_handoff_packet(
        self,
        owner_id: str = "default",
        session_id=None,
        status: str = "active",
        summary_text=None,
        payload_json=None,
        generated_at=None,
    ) -> str:
        """Insert a handoff_packet row and return its generated id."""
        from cortex.storage._helpers import _now
        async with self._write_lock:
            conn = await self._get_conn()
            now = _now()
            gen_at = generated_at or now
            async with conn.execute(
                """
                INSERT INTO handoff_packet
                    (owner_id, session_id, status, summary_text,
                     payload_json, generated_at, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                RETURNING id
                """,
                (owner_id, session_id, status, summary_text,
                 payload_json, gen_at, now, now),
            ) as cur:
                row = await cur.fetchone()
            await conn.commit()
        return row[0]

    async def get_handoff_packets(
        self,
        owner_id: str = "default",
        status=None,
        limit: int = 10,
    ):
        """Return recent handoff_packet rows as plain dicts."""
        conn = await self._get_conn()
        if status is not None:
            async with conn.execute(
                """
                SELECT * FROM handoff_packet
                WHERE owner_id = ? AND status = ?
                ORDER BY created_at DESC
                LIMIT ?
                """,
                (owner_id, status, limit),
            ) as cur:
                rows = await cur.fetchall()
        else:
            async with conn.execute(
                """
                SELECT * FROM handoff_packet
                WHERE owner_id = ?
                ORDER BY created_at DESC
                LIMIT ?
                """,
                (owner_id, limit),
            ) as cur:
                rows = await cur.fetchall()
        return [dict(r) for r in rows]
