"""
cortex/storage/migrations/v26_orphan_sessions.py — Orphan session status (R-321).

Marks batch-ingest chunk sessions as status='orphan' to prevent the dream
pipeline from wasting I/O iterating 3,084 sessions with zero claims.

A session is an orphan when:
  - It has 0 associated semantic claims (source_session FK never matched)
  - AND was created by batch ingest (session_id starts with 'batch-' or
    contains 'batch_' or looks like an ISO date e.g. '2026-03-12')

The dream pipeline skips sessions with status='orphan' at the
_dream_recover_orphans step (R-321 Fix 1 also guards this independently).

Migration: version 25 → 26

B-OS1 fix: Store previous_status before marking orphan so rollback is lossless.
B-OS2 fix: Tighten ISO-date GLOB to require 'T' separator (full ISO timestamp)
           and exclude sessions with claims (claimless guard is the real filter).
"""

VERSION = 26
DESCRIPTION = "Mark claimless batch-ingest sessions as status='orphan' to skip in dream pipeline (R-321)"

# SQL_UP is required by the migration runner's module validator.
# The actual work is done via apply() for fine-grained transaction control.
SQL_UP = """
-- Handled by apply() below for idempotent, fine-grained execution.
SELECT 1;
"""

# B-OS1 fix: SQL_DOWN is a no-op. We cannot blindly restore all orphans to
# 'completed' because their original status varied (sleeping, dreaming, completed,
# etc.). The previous_status is stored in the sleep_report JSON field during
# upgrade so operators can manually restore if needed, but automatic rollback
# of an informational status change is unsafe.
SQL_DOWN = """
-- NO-OP: Orphan marking is informational. Rollback requires manual review.
-- Original status is preserved in sleep_report JSON field as {"previous_status": "..."}.
-- To manually revert: UPDATE session_record SET status = json_extract(sleep_report, '$.previous_status') WHERE status = 'orphan' AND json_extract(sleep_report, '$.previous_status') IS NOT NULL;
SELECT 1;
"""

# B-OS2 fix: The ISO-date GLOB was over-broad — it matched any session_id starting
# with a date-like pattern (e.g. '2026-03-12-user-session'). Tightened to require
# the full batch-ingest ISO timestamp format: YYYY-MM-DDTHH:MM:SS (with 'T' separator).
# Additionally, the claimless guard (NOT EXISTS semantic_claim) is the real filter —
# the session_id pattern is just a safety net to avoid marking non-batch sessions.
_MARK_ORPHANS_SQL = """
UPDATE session_record
SET status = 'orphan',
    sleep_report = json_object('previous_status', status)
WHERE status NOT IN ('awake', 'orphan')
  AND NOT EXISTS (
      SELECT 1 FROM semantic_claim
      -- BUG FIX: Previously compared source_session (which stores the hex PK
      -- session_record.id) against session_record.session_id (the app-level
      -- UUID).  This caused ALL sessions to appear claimless, incorrectly
      -- marking sessions WITH claims as orphans.
      WHERE source_session = session_record.id
  )
  AND (
      session_id LIKE 'batch-%'
      OR session_id LIKE 'batch_%'
      OR session_id GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]*'
  )
"""


async def apply(conn) -> None:
    """Mark claimless batch-ingest sessions as orphan (idempotent).

    B-OS1: Stores previous status in sleep_report JSON before overwriting.
    B-OS2: Uses tightened GLOB pattern requiring ISO timestamp format with 'T'.
    """
    await conn.execute(_MARK_ORPHANS_SQL)
    await conn.commit()


async def upgrade(conn) -> None:
    """Alias for apply() — used by direct migration tooling."""
    await apply(conn)


async def downgrade(conn) -> None:
    """Revert orphan sessions using stored previous_status (lossless rollback).

    B-OS1 fix: Restores each session to its original status from the
    sleep_report JSON field, rather than blindly setting all to 'completed'.
    Sessions without a stored previous_status are left as-is (manual review needed).
    """
    await conn.execute("""
        UPDATE session_record
        SET status = json_extract(sleep_report, '$.previous_status'),
            sleep_report = NULL
        WHERE status = 'orphan'
          AND json_extract(sleep_report, '$.previous_status') IS NOT NULL
    """)
    await conn.commit()
