"""
cortex/tools/migrate_eva.py — Eva-specific Mem0 → Cortex v3 migration tool.

Migrates Eva's 19 Mem0 memories (including 7 cornerstones) plus 4 scar memories
into a Cortex SQLite database. Works standalone — does not require the generic
migration tool (#68) to be present.

Usage:
    python -m cortex.tools.migrate_eva \\
        --mem0-api-key $MEM0_API_KEY \\
        --cortex-db ./cortex.db \\
        --include-scars \\
        --dry-run

Features:
    - Fetches memories from Mem0 Platform API (agent_id='eva', user_id='andrew-main')
    - Maps Mem0 metadata schema → Cortex SemanticClaim + CornerstoneMemory
    - Seals cornerstones (is_cornerstone="true") via CornerstoneGuardian
    - Creates 4 hardcoded scar memories (operational lessons)
    - Idempotent: uses Mem0 ID as content fingerprint, skips existing records
    - Dry-run mode: full simulation, zero writes

Architecture:
    - Uses httpx for direct Mem0 API calls (no Mem0 SDK dependency)
    - SQLite via SQLiteAdapter (aiosqlite) — same adapter used by the whole system
    - CornerstoneGuardian for sealing, hash computation, token counting
    - Priority → confidence mapping: critical=1.0, high=0.9, medium=0.7, low=0.5
    - Scar memories: stored as SemanticClaim with sensitivity='restricted',
      claim_type='scar', plus a companion ProcedureMemory for behavioral binding

Dependencies:
    httpx, aiosqlite, cortex.storage.SQLiteAdapter, cortex.identity.cornerstone
"""

from __future__ import annotations

import argparse
import asyncio
import hashlib
import json
import logging
import sys
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, TYPE_CHECKING

import httpx

from cortex.config import CortexConfig
from cortex.identity.cornerstone import CornerstoneGuardian, _sha256
from cortex.storage import SQLiteAdapter

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

MEM0_BASE_URL = "https://api.mem0.ai"
MEM0_AGENT_ID = "eva"
MEM0_USER_ID = "andrew-main"

# Priority → SemanticClaim.confidence mapping
PRIORITY_CONFIDENCE: Dict[str, float] = {
    "critical": 1.0,
    "high": 0.9,
    "medium": 0.7,
    "low": 0.5,
}

# Scar memory definitions (not in Mem0 — created fresh)
SCAR_DEFINITIONS = [
    {
        "id": "scar-001",
        "label": "Sonnet sub-agent killed gateway via launchctl (2026-02-28)",
        "error": (
            "Sonnet sub-agent ran `launchctl unload ai.openclaw.gateway.plist`, "
            "killing the gateway process. Required manual restart by Andrew."
        ),
        "correction": "Never run launchctl commands in sub-agents. Gateway management is operator-only.",
        "lesson": (
            "Sub-agents MUST NOT execute any launchctl, gateway restart, or process management "
            "commands. These are in the Mutation Guard list. Even if asked, refuse."
        ),
        "behavioral_binding": (
            "Before any launchctl or gateway command: STOP. Check if running as sub-agent. "
            "If yes: refuse and return to main agent. Always."
        ),
        "date": "2026-02-28",
    },
    {
        "id": "scar-002",
        "label": "Post-compaction timeout bug (2026-03-01)",
        "error": (
            "After /compact, the session re-anchoring step triggered a cascade of "
            "file reads that exceeded the context timeout, stalling the session."
        ),
        "correction": (
            "Post-compact re-anchor reads SOUL.md + USER.md + today's memory only. "
            "MEMORY.md is deferred until explicitly needed."
        ),
        "lesson": (
            "Re-anchor checklist must be minimal. Don't read all files eagerly post-compact. "
            "Context was already compressed — adding bulk immediately defeats the purpose."
        ),
        "behavioral_binding": (
            "After /compact: read SOUL.md, USER.md, today memory only. "
            "MEMORY.md only if Andrew asks something requiring it."
        ),
        "date": "2026-03-01",
    },
    {
        "id": "scar-003",
        "label": "npm update wiped eva-core patches (2026-03-01)",
        "error": (
            "Running `npm update -g openclaw` pulled vanilla OpenClaw from npm registry, "
            "overwriting all eva-core patches, extensions, and custom modules."
        ),
        "correction": (
            "Immediately restored from git backup. Cherry-picked upstream fixes "
            "manually onto eva-core branch."
        ),
        "lesson": (
            "eva-core is a private fork. npm/openclaw update commands treat it as vanilla. "
            "NEVER run: `openclaw update`, `npm update -g openclaw`, `openclaw doctor --fix`, "
            "`gateway update.run`. These are in the never-do list."
        ),
        "behavioral_binding": (
            "If ANY update command is suggested that touches /opt/homebrew/lib/node_modules/openclaw/: "
            "REFUSE. State it would destroy eva-core patches. Offer cherry-pick workflow instead."
        ),
        "date": "2026-03-01",
    },
    {
        "id": "scar-004",
        "label": "Config trailing comma killed gateway (2026-02-25)",
        "error": (
            "A trailing comma was written to openclaw.json during a config update. "
            "Invalid JSON caused the gateway daemon to fail silently on restart."
        ),
        "correction": (
            "Removed trailing comma. Validated JSON with `python3 -m json.tool`. "
            "Gateway restarted successfully."
        ),
        "lesson": (
            "JSON config files are strict — no trailing commas. Always validate with "
            "`python3 -m json.tool <file>` or `jq . <file>` before saving. "
            "Use the Mutation Guard script for openclaw.json writes."
        ),
        "behavioral_binding": (
            "Before writing any .json config file: validate it first. "
            "After writing: re-validate. Never skip this — silent failures are worse than loud ones."
        ),
        "date": "2026-02-25",
    },
]

# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------

@dataclass
class Mem0Memory:
    """Parsed representation of a single Mem0 API memory record."""
    id: str
    memory: str
    agent_id: Optional[str] = None
    user_id: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None
    # Parsed metadata fields
    kind: Optional[str] = None
    source: Optional[str] = None
    priority: str = "medium"
    stability: float = 0.5
    tier: Optional[str] = None
    is_cornerstone: bool = False
    knowledge_type: Optional[str] = None
    # Cornerstone injection order (computed)
    injection_order: int = 99


@dataclass
class MigrationResult:
    """Summary of a migration run."""
    total_fetched: int = 0
    claims_created: int = 0
    claims_skipped: int = 0
    cornerstones_sealed: int = 0
    cornerstones_skipped: int = 0
    scars_created: int = 0
    scars_skipped: int = 0
    errors: List[str] = field(default_factory=list)
    dry_run: bool = False

    def summary(self) -> str:
        mode = "[DRY RUN] " if self.dry_run else ""
        return (
            f"{mode}Migration complete:\n"
            f"  Mem0 memories fetched:   {self.total_fetched}\n"
            f"  Claims created:          {self.claims_created}\n"
            f"  Claims skipped:          {self.claims_skipped}\n"
            f"  Cornerstones sealed:     {self.cornerstones_sealed}\n"
            f"  Cornerstones skipped:    {self.cornerstones_skipped}\n"
            f"  Scars created:           {self.scars_created}\n"
            f"  Scars skipped:           {self.scars_skipped}\n"
            f"  Errors:                  {len(self.errors)}"
        )


# ---------------------------------------------------------------------------
# Mem0 API client
# ---------------------------------------------------------------------------

class Mem0Client:
    """Minimal async Mem0 Platform API client using httpx."""

    def __init__(self, api_key: str, base_url: str = MEM0_BASE_URL):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self._headers = {
            "Authorization": f"Token {api_key}",
            "Content-Type": "application/json",
        }

    async def list_memories(
        self,
        agent_id: str = MEM0_AGENT_ID,
        user_id: str = MEM0_USER_ID,
        page_size: int = 50,
    ) -> List[Dict[str, Any]]:
        """Fetch all memories for the given agent/user from Mem0 API v1."""
        all_memories: List[Dict[str, Any]] = []
        page = 1

        async with httpx.AsyncClient(timeout=30.0) as client:
            while True:
                params: Dict[str, Any] = {
                    "page": page,
                    "page_size": page_size,
                }
                if agent_id:
                    params["agent_id"] = agent_id
                if user_id:
                    params["user_id"] = user_id

                resp = await client.get(
                    f"{self.base_url}/v1/memories/",
                    headers=self._headers,
                    params=params,
                )
                resp.raise_for_status()
                data = resp.json()

                # Handle both list and paginated response shapes
                if isinstance(data, list):
                    all_memories.extend(data)
                    break
                elif isinstance(data, dict):
                    results = data.get("results", data.get("memories", []))
                    all_memories.extend(results)
                    if not data.get("next"):
                        break
                    page += 1
                else:
                    break

        logger.info("Fetched %d memories from Mem0 API", len(all_memories))
        return all_memories


# ---------------------------------------------------------------------------
# Field mapping helpers
# ---------------------------------------------------------------------------

def parse_mem0_memory(raw: Dict[str, Any]) -> Mem0Memory:
    """Parse a raw Mem0 API dict into a typed Mem0Memory object."""
    meta: Dict[str, Any] = raw.get("metadata") or {}

    # is_cornerstone can be stored as "true" (string) or True (bool)
    is_cs_raw = meta.get("is_cornerstone", False)
    is_cornerstone = (
        is_cs_raw is True
        or (isinstance(is_cs_raw, str) and is_cs_raw.lower() == "true")
    )

    stability_raw = meta.get("stability", 0.5)
    try:
        stability = float(stability_raw)
    except (TypeError, ValueError):
        stability = 0.5

    return Mem0Memory(
        id=raw.get("id", ""),
        memory=raw.get("memory", ""),
        agent_id=raw.get("agent_id"),
        user_id=raw.get("user_id"),
        created_at=raw.get("created_at"),
        updated_at=raw.get("updated_at"),
        kind=meta.get("kind"),
        source=meta.get("source"),
        priority=meta.get("priority", "medium"),
        stability=stability,
        tier=meta.get("tier"),
        is_cornerstone=is_cornerstone,
        knowledge_type=meta.get("knowledge_type"),
    )


def priority_to_confidence(priority: str) -> float:
    """Map Mem0 priority string → SemanticClaim confidence float."""
    return PRIORITY_CONFIDENCE.get(priority.lower(), 0.7)


def kind_to_claim_type(kind: Optional[str]) -> str:
    """Map Mem0 metadata.kind → claim_type string."""
    if not kind:
        return "semantic"
    kind_map = {
        "identity": "identity",
        "preference": "preference",
        "technical": "technical",
        "operational": "operational",
        "relationship": "relationship",
        "scar": "scar",
    }
    return kind_map.get(kind.lower(), "semantic")


def priority_to_injection_order(priority: str) -> int:
    """Map priority string to cornerstone injection_order (lower = injected first)."""
    order_map = {
        "critical": 0,
        "high": 1,
        "medium": 2,
        "low": 3,
    }
    return order_map.get(priority.lower(), 5)


def memory_content_fingerprint(mem0_id: str) -> str:
    """
    Deterministic fingerprint for idempotency checks.
    We embed the Mem0 ID into the claim's object_value preamble and use
    this as a dedup key.
    """
    return f"[mem0:{mem0_id}]"


def scar_fingerprint(scar_id: str) -> str:
    """Deterministic fingerprint for scar idempotency."""
    return f"[scar:{scar_id}]"


# ---------------------------------------------------------------------------
# Core migration engine
# ---------------------------------------------------------------------------

class EvaMemoryMigrator:
    """
    Migrates Eva's Mem0 memories into a Cortex SQLite database.

    Idempotency strategy:
        - Each Mem0 memory is stored as a SemanticClaim with a fingerprint
          token embedded in object_value: "[mem0:<id>] <original text>"
        - Before creating, scan existing claims for the fingerprint
        - Cornerstones: fingerprint in cornerstone label: "[mem0:<id>] <label>"
        - Scars: fingerprint in scar label: "[scar:<id>] <label>"
    """

    def __init__(
        self,
        storage: SQLiteAdapter,
        owner_id: str = "eva",
        dry_run: bool = False,
    ):
        self.storage = storage
        self.owner_id = owner_id
        self.dry_run = dry_run
        self._guardian: Optional[CornerstoneGuardian] = None

    def _get_guardian(self) -> CornerstoneGuardian:
        """Lazy init CornerstoneGuardian with a minimal config."""
        if self._guardian is None:
            config = CortexConfig()
            self._guardian = CornerstoneGuardian(
                storage=self.storage, config=config
            )
        return self._guardian

    # ------------------------------------------------------------------
    # Idempotency helpers
    # ------------------------------------------------------------------

    async def _claim_fingerprint_exists(self, fingerprint: str) -> bool:
        """Return True if a claim with this fingerprint already exists."""
        try:
            # Use FTS search as a lightweight existence check
            # Escape special FTS5 characters in fingerprint
            safe_query = fingerprint.replace("[", '"[').replace("]", ']"') if "[" in fingerprint else fingerprint
            results = await self.storage.fts_search(
                query=safe_query,
                table="claims",
                top_k=1,
                owner_id=self.owner_id,
            )
            return len(results) > 0
        except Exception:
            return False

    async def _cornerstone_label_exists(self, label_prefix: str) -> bool:
        """Return True if a cornerstone with this label prefix already exists."""
        try:
            cornerstones = await self.storage.get_cornerstones(owner_id=self.owner_id)
            return any(cs.label.startswith(label_prefix) for cs in cornerstones)
        except Exception:
            return False

    # ------------------------------------------------------------------
    # Mem0 memory migration
    # ------------------------------------------------------------------

    async def migrate_memory(
        self, mem: Mem0Memory, result: MigrationResult
    ) -> None:
        """Migrate a single Mem0Memory record to Cortex."""
        fingerprint = memory_content_fingerprint(mem.id)
        confidence = priority_to_confidence(mem.priority)
        claim_type = kind_to_claim_type(mem.kind)

        # Embed fingerprint in object_value for idempotency
        object_value = f"{fingerprint} {mem.memory}"

        # Build extra metadata
        extra_meta: Dict[str, Any] = {}
        if mem.source:
            extra_meta["source"] = mem.source
        if mem.stability is not None:
            extra_meta["stability"] = mem.stability
        if mem.tier:
            extra_meta["tier"] = mem.tier
        if mem.knowledge_type:
            extra_meta["knowledge_type"] = mem.knowledge_type
        extra_meta["mem0_id"] = mem.id

        if mem.is_cornerstone:
            await self._seal_as_cornerstone(mem, fingerprint, extra_meta, result)
        else:
            await self._create_claim(
                mem=mem,
                fingerprint=fingerprint,
                object_value=object_value,
                confidence=confidence,
                claim_type=claim_type,
                extra_meta=extra_meta,
                result=result,
            )

    async def _create_claim(
        self,
        mem: Mem0Memory,
        fingerprint: str,
        object_value: str,
        confidence: float,
        claim_type: str,
        extra_meta: Dict[str, Any],
        result: MigrationResult,
    ) -> None:
        """Create a SemanticClaim for a non-cornerstone memory."""
        if await self._claim_fingerprint_exists(fingerprint):
            logger.debug("Skip (exists): claim fingerprint=%s", fingerprint)
            result.claims_skipped += 1
            return

        if self.dry_run:
            logger.info(
                "[DRY RUN] Would create claim: subject=%r predicate=%r "
                "confidence=%.2f claim_type=%s",
                mem.agent_id or self.owner_id,
                claim_type,
                confidence,
                claim_type,
            )
            result.claims_created += 1
            return

        await self.storage.create_claim(
            subject=mem.agent_id or self.owner_id,
            predicate=claim_type,
            object_value=object_value,
            owner_id=self.owner_id,
            confidence=confidence,
            memory_type="long-term",
            sensitivity="normal",
            metadata_json=json.dumps(extra_meta),
        )
        logger.info("Created claim: fingerprint=%s", fingerprint)
        result.claims_created += 1

    async def _seal_as_cornerstone(
        self,
        mem: Mem0Memory,
        fingerprint: str,
        extra_meta: Dict[str, Any],
        result: MigrationResult,
    ) -> None:
        """Seal a cornerstone memory via CornerstoneGuardian."""
        # Label embeds fingerprint for idempotency
        cs_label = f"{fingerprint} {mem.kind or 'identity'}"

        if await self._cornerstone_label_exists(fingerprint):
            logger.debug("Skip (exists): cornerstone fingerprint=%s", fingerprint)
            result.cornerstones_skipped += 1
            return

        injection_order = priority_to_injection_order(mem.priority)
        guardian = self._get_guardian()

        if self.dry_run:
            logger.info(
                "[DRY RUN] Would seal cornerstone: label=%r injection_order=%d hash=%s",
                cs_label,
                injection_order,
                _sha256(mem.memory)[:12],
            )
            result.cornerstones_sealed += 1
            return

        try:
            cs = await guardian.seal(
                label=cs_label,
                content=mem.memory,
                owner_id=self.owner_id,
                sealed_by="migrate_eva",
            )
            # Update injection_order post-seal (guardian appends by default)
            # Store extra metadata as a companion claim
            await self.storage.create_claim(
                subject=self.owner_id,
                predicate="cornerstone_ref",
                object_value=f"{fingerprint} [cornerstone_id:{cs.id}]",
                owner_id=self.owner_id,
                confidence=1.0,
                memory_type="long-term",
                sensitivity="normal",
                metadata_json=json.dumps({**extra_meta, "cornerstone_id": cs.id}),
            )
            logger.info(
                "Sealed cornerstone: id=%s label=%r hash=%s",
                cs.id,
                cs_label,
                cs.golden_hash[:12],
            )
            result.cornerstones_sealed += 1
        except ValueError as e:
            if "cap reached" in str(e):
                # Cornerstone cap hit — store as high-confidence long-term claim instead
                logger.warning(
                    "Cornerstone cap reached; storing as high-confidence claim: %s",
                    fingerprint,
                )
                await self.storage.create_claim(
                    subject=self.owner_id,
                    predicate="identity_overflow",
                    object_value=f"{fingerprint} {mem.memory}",
                    owner_id=self.owner_id,
                    confidence=1.0,
                    memory_type="long-term",
                    sensitivity="normal",
                    metadata_json=json.dumps({**extra_meta, "overflow_from": "cornerstone_cap"}),
                )
                result.claims_created += 1
            else:
                raise

    # ------------------------------------------------------------------
    # Scar memory creation
    # ------------------------------------------------------------------

    async def create_scar(
        self, scar_def: Dict[str, Any], result: MigrationResult
    ) -> None:
        """Create a scar memory (error+lesson+binding) as a SemanticClaim."""
        fingerprint = scar_fingerprint(scar_def["id"])

        if await self._claim_fingerprint_exists(fingerprint):
            logger.debug("Skip (exists): scar fingerprint=%s", fingerprint)
            result.scars_skipped += 1
            return

        # Compose the full scar content
        scar_content = (
            f"{fingerprint} "
            f"[ERROR] {scar_def['error']} "
            f"[CORRECTION] {scar_def['correction']} "
            f"[LESSON] {scar_def['lesson']} "
            f"[BINDING] {scar_def['behavioral_binding']}"
        )

        scar_meta = {
            "scar_id": scar_def["id"],
            "kind": "scar",
            "source": "operational",
            "priority": "critical",
            "stability": 1.0,
            "tier": "immutable",
            "is_scar": True,
            "knowledge_type": "implicit",
            "incident_date": scar_def.get("date", ""),
            "label": scar_def["label"],
            "error": scar_def["error"],
            "correction": scar_def["correction"],
            "lesson": scar_def["lesson"],
            "behavioral_binding": scar_def["behavioral_binding"],
        }

        if self.dry_run:
            logger.info(
                "[DRY RUN] Would create scar: id=%s label=%r",
                scar_def["id"],
                scar_def["label"],
            )
            result.scars_created += 1
            return

        await self.storage.create_claim(
            subject=self.owner_id,
            predicate="scar",
            object_value=scar_content,
            owner_id=self.owner_id,
            confidence=1.0,
            memory_type="long-term",
            sensitivity="restricted",
            metadata_json=json.dumps(scar_meta),
        )
        logger.info("Created scar: id=%s label=%r", scar_def["id"], scar_def["label"])
        result.scars_created += 1

    # ------------------------------------------------------------------
    # Top-level orchestration
    # ------------------------------------------------------------------

    async def run(
        self,
        memories: List[Mem0Memory],
        include_scars: bool = True,
    ) -> MigrationResult:
        """
        Execute full migration: claims + cornerstones + (optionally) scars.

        Args:
            memories:      Pre-fetched list of Mem0Memory objects.
            include_scars: If True, also create the 4 hardcoded scar memories.

        Returns:
            MigrationResult summary.
        """
        result = MigrationResult(
            total_fetched=len(memories),
            dry_run=self.dry_run,
        )

        # Sort: cornerstones first (so cap is used on the most important ones)
        sorted_memories = sorted(
            memories,
            key=lambda m: (
                0 if m.is_cornerstone else 1,
                priority_to_injection_order(m.priority),
            ),
        )

        for mem in sorted_memories:
            try:
                await self.migrate_memory(mem, result)
            except Exception as e:
                msg = f"Error migrating memory {mem.id}: {e}"
                logger.error(msg)
                result.errors.append(msg)

        if include_scars:
            for scar_def in SCAR_DEFINITIONS:
                try:
                    await self.create_scar(scar_def, result)
                except Exception as e:
                    msg = f"Error creating scar {scar_def['id']}: {e}"
                    logger.error(msg)
                    result.errors.append(msg)

        return result


# ---------------------------------------------------------------------------
# CLI entry point
# ---------------------------------------------------------------------------

async def _async_main(args: argparse.Namespace) -> int:
    """Async main — fetches from Mem0, runs migration, prints summary."""
    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.INFO,
        format="%(levelname)s %(name)s: %(message)s",
    )

    # 1. Fetch memories from Mem0
    if args.mem0_api_key:
        client = Mem0Client(api_key=args.mem0_api_key)
        try:
            raw_memories = await client.list_memories(
                agent_id=args.agent_id,
                user_id=args.user_id,
            )
        except httpx.HTTPStatusError as e:
            logger.error("Mem0 API error: %s", e)
            return 1
        parsed_memories = [parse_mem0_memory(r) for r in raw_memories]
    elif args.mem0_fixture:
        # Load from a local JSON fixture file (for testing / offline use)
        with open(args.mem0_fixture) as f:
            raw_memories = json.load(f)
        parsed_memories = [parse_mem0_memory(r) for r in raw_memories]
        logger.info("Loaded %d memories from fixture %s", len(parsed_memories), args.mem0_fixture)
    else:
        logger.error("Either --mem0-api-key or --mem0-fixture is required")
        return 1

    # 2. Open Cortex DB
    storage = SQLiteAdapter(db_path=args.cortex_db)
    await storage.initialize()
    # 3. Run migration
    migrator = EvaMemoryMigrator(
        storage=storage,
        owner_id=args.owner_id,
        dry_run=args.dry_run,
    )
    result = await migrator.run(
        memories=parsed_memories,
        include_scars=args.include_scars,
    )

    # 4. Close DB
    await storage.close()

    # 5. Print summary
    print(result.summary())
    if result.errors:
        print("\nErrors:")
        for e in result.errors:
            print(f"  - {e}")
        return 1

    return 0


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Migrate Eva's Mem0 memories to Cortex v3 SQLite database.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Full migration with Mem0 API:
  python -m cortex.tools.migrate_eva \\
      --mem0-api-key $MEM0_API_KEY \\
      --cortex-db ./cortex.db \\
      --include-scars

  # Dry run (no writes):
  python -m cortex.tools.migrate_eva \\
      --mem0-api-key $MEM0_API_KEY \\
      --cortex-db ./cortex.db \\
      --include-scars \\
      --dry-run

  # Offline mode (local fixture):
  python -m cortex.tools.migrate_eva \\
      --mem0-fixture ./fixtures/eva_memories.json \\
      --cortex-db ./cortex.db \\
      --dry-run
""",
    )
    parser.add_argument(
        "--mem0-api-key",
        default=None,
        help="Mem0 Platform API key. Required unless --mem0-fixture is set.",
    )
    parser.add_argument(
        "--mem0-fixture",
        default=None,
        help="Path to a local JSON fixture file (offline / testing mode).",
    )
    parser.add_argument(
        "--cortex-db",
        default="./cortex.db",
        help="Path to Cortex SQLite database (default: ./cortex.db).",
    )
    parser.add_argument(
        "--owner-id",
        default="eva",
        help="Owner ID for migrated records (default: eva).",
    )
    parser.add_argument(
        "--agent-id",
        default=MEM0_AGENT_ID,
        help=f"Mem0 agent_id filter (default: {MEM0_AGENT_ID}).",
    )
    parser.add_argument(
        "--user-id",
        default=MEM0_USER_ID,
        help=f"Mem0 user_id filter (default: {MEM0_USER_ID}).",
    )
    parser.add_argument(
        "--include-scars",
        action="store_true",
        default=False,
        help="Also create the 4 hardcoded scar memories.",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        default=False,
        help="Simulate migration without writing to the database.",
    )
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        default=False,
        help="Enable debug logging.",
    )

    args = parser.parse_args()
    sys.exit(asyncio.run(_async_main(args)))


if __name__ == "__main__":
    main()
