"""
EMU Sync Commands - IaC-style synchronization for EMUs.

Commands:
    memrail emu-pull ./emus/ -w prod -p api   # Pull remote EMUs to local JSONL
    memrail emu-plan ./emus/                   # Show diff between local and remote
    memrail emu-apply ./emus/                  # Push local changes to remote
    memrail emu-diff ./emus/ <emu_key>         # Show detailed diff for specific EMU
"""
from __future__ import annotations

import hashlib
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, List, Dict, Any

import typer
from dotenv import load_dotenv
from rich.console import Console
from rich.table import Table
from rich.panel import Panel

console = Console()


def load_environment() -> None:
    """Load environment variables from .env file."""
    # Search for .env file in various locations
    env_paths = [
        Path.cwd() / ".env",
        Path.cwd().parent / ".env",
        Path(__file__).parent.parent / ".env",  # soma-ami-python-sdk/.env
        Path(__file__).parent.parent.parent / ".env",  # memrail-api/.env
    ]
    for env_path in env_paths:
        if env_path.exists():
            load_dotenv(env_path, override=True)
            console.print(f"[dim]Loaded environment from {env_path}[/dim]")
            return
    console.print("[yellow]Warning: No .env file found[/yellow]")


# =============================================================================
# MODELS
# =============================================================================

class LockMeta:
    """Metadata from lock file."""
    def __init__(self, workspace: str, project: str, pulled_at: str):
        self.workspace = workspace
        self.project = project
        self.pulled_at = pulled_at

    def to_dict(self) -> Dict[str, Any]:
        return {
            "_meta": True,
            "workspace": self.workspace,
            "project": self.project,
            "pulled_at": self.pulled_at,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "LockMeta":
        return cls(
            workspace=data.get("workspace", ""),
            project=data.get("project", ""),
            pulled_at=data.get("pulled_at", ""),
        )


class LockEntry:
    """EMU entry in lock file."""
    def __init__(
        self,
        emu_key: str,
        version: int,
        emu_id: str,
        state: str,
        content_hash: str,
        deployed_at: str,
    ):
        self.emu_key = emu_key
        self.version = version
        self.emu_id = emu_id
        self.state = state
        self.content_hash = content_hash
        self.deployed_at = deployed_at

    def to_dict(self) -> Dict[str, Any]:
        return {
            "emu_key": self.emu_key,
            "version": self.version,
            "emu_id": self.emu_id,
            "state": self.state,
            "content_hash": self.content_hash,
            "deployed_at": self.deployed_at,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "LockEntry":
        return cls(
            emu_key=data.get("emu_key", ""),
            version=data.get("version", 0),
            emu_id=data.get("emu_id", ""),
            state=data.get("state", ""),
            content_hash=data.get("content_hash", ""),
            deployed_at=data.get("deployed_at", ""),
        )


class LocalEMU:
    """EMU definition from local JSONL file."""
    def __init__(
        self,
        emu_key: str,
        trigger: str,
        action: Dict[str, Any],
        expected_utility: float,
        confidence: float,
        policy: Optional[Dict[str, Any]] = None,
        intent: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        state: Optional[str] = None,
        source_file: Optional[str] = None,
        source_line: Optional[int] = None,
    ):
        self.emu_key = emu_key
        self.trigger = trigger
        self.action = action
        self.expected_utility = expected_utility
        self.confidence = confidence
        self.policy = policy or {}
        self.intent = intent
        self.metadata = metadata or {}
        self.state = state or "draft"
        self.source_file = source_file
        self.source_line = source_line

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dict for JSONL serialization."""
        result = {
            "emu_key": self.emu_key,
            "trigger": self.trigger,
            "action": self.action,
            "expected_utility": self.expected_utility,
            "confidence": self.confidence,
        }
        if self.policy:
            result["policy"] = self.policy
        if self.intent:
            result["intent"] = self.intent
        if self.metadata:
            result["metadata"] = self.metadata
        if self.state and self.state != "draft":
            result["state"] = self.state
        return result

    def content_hash(self) -> str:
        """Compute content hash for change detection."""
        # Exclude source_file/source_line from hash
        data = self.to_dict()
        canonical = json.dumps(data, sort_keys=True, separators=(",", ":"))
        return f"sha256:{hashlib.sha256(canonical.encode()).hexdigest()[:16]}"

    @classmethod
    def from_dict(
        cls,
        data: Dict[str, Any],
        source_file: Optional[str] = None,
        source_line: Optional[int] = None,
    ) -> "LocalEMU":
        return cls(
            emu_key=data.get("emu_key", ""),
            trigger=data.get("trigger", ""),
            action=data.get("action", {}),
            expected_utility=data.get("expected_utility", 0.8),
            confidence=data.get("confidence", 0.9),
            policy=data.get("policy"),
            intent=data.get("intent"),
            metadata=data.get("metadata"),
            state=data.get("state"),
            source_file=source_file,
            source_line=source_line,
        )


# =============================================================================
# FILE I/O
# =============================================================================

def scan_jsonl_files(folder: Path) -> List[Path]:
    """Find all .jsonl files in folder (non-recursive, excludes lock file)."""
    if not folder.exists():
        return []
    return sorted([
        f for f in folder.glob("*.jsonl")
        if not f.name.startswith(".") and f.name != ".emu.lock.jsonl"
    ])


def read_local_emus(folder: Path) -> List[LocalEMU]:
    """Read all EMUs from JSONL files in folder."""
    emus = []
    jsonl_files = scan_jsonl_files(folder)

    for jsonl_file in jsonl_files:
        with open(jsonl_file, "r") as f:
            for line_num, line in enumerate(f, start=1):
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                try:
                    data = json.loads(line)
                    emu = LocalEMU.from_dict(
                        data,
                        source_file=jsonl_file.name,
                        source_line=line_num,
                    )
                    emus.append(emu)
                except json.JSONDecodeError as e:
                    console.print(f"[red]Error:[/red] Invalid JSON in {jsonl_file.name}:{line_num}: {e}")
                    raise typer.Exit(1)

    return emus


def read_lock_file(folder: Path) -> tuple[Optional[LockMeta], Dict[str, LockEntry]]:
    """Read lock file from folder."""
    lock_path = folder / ".emu.lock.jsonl"
    if not lock_path.exists():
        return None, {}

    meta = None
    entries = {}

    with open(lock_path, "r") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            data = json.loads(line)
            if data.get("_meta"):
                meta = LockMeta.from_dict(data)
            else:
                entry = LockEntry.from_dict(data)
                entries[entry.emu_key] = entry

    return meta, entries


def write_lock_file(
    folder: Path,
    meta: LockMeta,
    entries: Dict[str, LockEntry],
) -> None:
    """Write lock file to folder."""
    lock_path = folder / ".emu.lock.jsonl"

    with open(lock_path, "w") as f:
        # Write metadata first
        f.write(json.dumps(meta.to_dict(), separators=(",", ":")) + "\n")
        # Write entries sorted by emu_key
        for emu_key in sorted(entries.keys()):
            f.write(json.dumps(entries[emu_key].to_dict(), separators=(",", ":")) + "\n")


def write_emus_jsonl(
    folder: Path,
    emus: List[Dict[str, Any]],
    filename: str = "emus.jsonl",
) -> Path:
    """Write EMUs to JSONL file."""
    output_path = folder / filename

    with open(output_path, "w") as f:
        for emu in sorted(emus, key=lambda e: e.get("emu_key", "")):
            f.write(json.dumps(emu, separators=(",", ":")) + "\n")

    return output_path


# =============================================================================
# HELPERS
# =============================================================================

def get_ami_client(
    workspace: Optional[str] = None,
    project: Optional[str] = None,
):
    """Get an AMI client instance."""
    from memrail import AMIClient

    api_key = os.environ.get("AMI_API_KEY")
    base_url = os.environ.get("AMI_BASE_URL")
    org = os.environ.get("AMI_ORG")

    if not api_key:
        console.print("[red]Error:[/red] AMI_API_KEY not set. Configure in .env or environment.")
        raise typer.Exit(1)

    # Debug: show connection info
    console.print(f"[dim]Connecting to {base_url or 'https://api.memrail.com'} (org={org})[/dim]")

    return AMIClient(
        api_key=api_key,
        base_url=base_url,
        org=org,
        team=os.environ.get("AMI_TEAM"),
        workspace=workspace or os.environ.get("AMI_WORKSPACE"),
        project=project or os.environ.get("AMI_PROJECT"),
    )


def resolve_target(
    folder: Path,
    workspace: Optional[str],
    project: Optional[str],
) -> tuple[str, str, str]:
    """
    Resolve target workspace/project.

    Returns: (workspace, project, source) where source is "flag", "lock", "env", etc.
    """
    # 1. Flags take priority
    if workspace and project:
        return workspace, project, "flags"

    # 2. Lock file
    meta, _ = read_lock_file(folder)
    if meta and meta.workspace and meta.project:
        return meta.workspace, meta.project, "lock file"

    # 3. Folder config (.ami.toml) - TODO: implement

    # 4. Environment variables
    env_ws = os.environ.get("AMI_WORKSPACE")
    env_proj = os.environ.get("AMI_PROJECT")
    if env_ws and env_proj:
        return env_ws, env_proj, "environment"

    # No target found
    console.print(
        "[red]Error:[/red] No target specified. "
        "Use --workspace/-w and --project/-p, or run 'memrail emu-pull' first to create lock file."
    )
    raise typer.Exit(1)


def dedup_latest_versions(emus: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """Keep only the latest version per emu_key from a list of remote EMU dicts."""
    latest_by_key: Dict[str, Any] = {}
    for emu in emus:
        key = emu.get("emu_key", "")
        ver = emu.get("version", 0)
        if key not in latest_by_key or ver > latest_by_key[key].get("version", 0):
            latest_by_key[key] = emu
    return list(latest_by_key.values())


def check_duplicates(emus: List[LocalEMU]) -> None:
    """Check for duplicate emu_keys across files."""
    seen: Dict[str, tuple[str, int]] = {}

    for emu in emus:
        if emu.emu_key in seen:
            prev_file, prev_line = seen[emu.emu_key]
            console.print(
                f"[red]Error:[/red] Duplicate emu_key '{emu.emu_key}' "
                f"in {emu.source_file}:{emu.source_line} and {prev_file}:{prev_line}"
            )
            raise typer.Exit(1)
        seen[emu.emu_key] = (emu.source_file or "?", emu.source_line or 0)


def validate_dsl(emus: List[LocalEMU]) -> bool:
    """Validate DSL syntax for all EMUs."""
    from memrail.dsl import validate_emu, DSLSyntaxError, DSLValidationError

    all_valid = True

    for emu in emus:
        try:
            validate_emu({"trigger": emu.trigger})
        except (DSLSyntaxError, DSLValidationError) as e:
            console.print(
                f"[red]Error:[/red] DSL syntax error in {emu.source_file}:{emu.source_line} "
                f"({emu.emu_key}): {e}"
            )
            all_valid = False

    return all_valid


# =============================================================================
# VALIDATION DISPLAY
# =============================================================================

def display_validation_results(
    reports: List[Dict[str, Any]],
    emu_keys: List[str],
    json_output: bool = False,
) -> tuple[int, int]:
    """
    Display validation results for a set of EMUs.

    Args:
        reports: List of {emu_key, state, report} dicts from bulk validation
        emu_keys: EMU keys to filter (only show these)
        json_output: If True, skip rich output (caller handles JSON)

    Returns:
        (pass_count, warn_count)
    """
    reports_by_key = {r.get("emu_key"): r for r in reports}
    pass_count = 0
    warn_count = 0

    for emu_key in emu_keys:
        item = reports_by_key.get(emu_key)
        if not item:
            if not json_output:
                console.print(f"    [dim]validation: not available (EMU not yet registered)[/dim]")
            continue

        report = item.get("report", {})
        passed = report.get("passed", True)
        warnings = report.get("warnings", [])
        reachability = report.get("reachability", {})

        if passed and not warnings:
            pass_count += 1
            if not json_output:
                total_deps = reachability.get("total_dependencies", 0)
                reachable = reachability.get("reachable_dependencies", 0)
                console.print(f"    [green]\u2713 Validation passed ({reachable}/{total_deps} dependencies reachable)[/green]")
        else:
            warn_count += 1
            if not json_output:
                for w in warnings:
                    severity = w.get("severity", "warning")
                    code = w.get("code", "UNKNOWN")
                    message = w.get("message", "")
                    color = "red" if severity == "error" else "yellow"
                    console.print(f"    [{color}]\u26a0 {code}:[/{color}] {message}")
                    remediation = w.get("remediation")
                    if remediation:
                        console.print(f"      [dim]Fix: {remediation}[/dim]")

    return pass_count, warn_count


# =============================================================================
# COMMANDS
# =============================================================================

def emu_pull(
    folder: Path = typer.Argument(
        ".",
        help="Destination folder for JSONL files (default: current directory)",
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Source workspace (required, or set via AMI_WORKSPACE env var)",
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Source project (required, or set via AMI_PROJECT env var)",
    ),
    output: str = typer.Option(
        "emus.jsonl",
        "--output", "-o",
        help="Output filename",
    ),
    filter_pattern: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Glob pattern to filter EMU keys (e.g., 'vip_*')",
    ),
    include_archived: bool = typer.Option(
        False,
        "--include-archived",
        help="Include archived EMUs",
    ),
):
    """
    Export remote EMUs to local JSONL.

    Examples:
        memrail emu-pull ./emus/ -w prod -p api
        memrail emu-pull ./emus/ -w prod -p api --filter "vip_*"
    """
    import fnmatch

    load_environment()

    # Resolve workspace/project
    ws = workspace or os.environ.get("AMI_WORKSPACE")
    proj = project or os.environ.get("AMI_PROJECT")

    if not ws or not proj:
        console.print(
            "[red]Error:[/red] --workspace/-w and --project/-p are required "
            "(or set AMI_WORKSPACE and AMI_PROJECT environment variables)"
        )
        raise typer.Exit(1)

    # Create folder if it doesn't exist
    folder.mkdir(parents=True, exist_ok=True)

    console.print(f"[dim]Pulling EMUs from workspace={ws}, project={proj}...[/dim]")

    # Fetch EMUs from API
    client = get_ami_client(workspace=ws, project=proj)

    try:
        org = os.environ.get("AMI_ORG")
        team = os.environ.get("AMI_TEAM")
        result = client.list_emus(org=org, team=team, workspace=ws, project=proj)
        # list_emus returns {"emus": [...], "total": n, ...}
        remote_emus = result.get("emus", []) if isinstance(result, dict) else result
    except Exception as e:
        console.print(f"[red]Error:[/red] Failed to fetch EMUs: {e}")
        raise typer.Exit(1)

    # Filter by pattern
    if filter_pattern:
        remote_emus = [
            e for e in remote_emus
            if fnmatch.fnmatch(e.get("emu_key", ""), filter_pattern)
        ]

    # Deduplicate: keep only the latest version per emu_key
    remote_emus = dedup_latest_versions(remote_emus)

    # Filter out archived unless requested
    if not include_archived:
        remote_emus = [
            e for e in remote_emus
            if e.get("state", "").lower() != "archived"
        ]

    if not remote_emus:
        console.print("[yellow]No EMUs found matching criteria[/yellow]")
        raise typer.Exit(0)

    # Convert to JSONL format (only the fields we care about)
    jsonl_emus = []
    lock_entries = {}

    for emu in remote_emus:
        emu_key = emu.get("emu_key", "")

        # JSONL format (minimal, for editing)
        jsonl_emu = {
            "emu_key": emu_key,
            "trigger": emu.get("trigger", ""),
            "action": emu.get("action", {}),
            "expected_utility": emu.get("expected_utility", 0.8),
            "confidence": emu.get("confidence", 0.9),
        }
        if emu.get("policy"):
            jsonl_emu["policy"] = emu.get("policy")
        if emu.get("intent"):
            jsonl_emu["intent"] = emu.get("intent")
        if emu.get("metadata"):
            jsonl_emu["metadata"] = emu.get("metadata")
        remote_state = emu.get("state", "draft").lower()
        if remote_state and remote_state != "draft":
            jsonl_emu["state"] = remote_state

        jsonl_emus.append(jsonl_emu)

        # Lock entry (full state)
        canonical = json.dumps(jsonl_emu, sort_keys=True, separators=(",", ":"))
        content_hash = f"sha256:{hashlib.sha256(canonical.encode()).hexdigest()[:16]}"

        lock_entries[emu_key] = LockEntry(
            emu_key=emu_key,
            version=emu.get("version", 1),
            emu_id=emu.get("emu_id", ""),
            state=emu.get("state", "draft"),
            content_hash=content_hash,
            deployed_at=emu.get("updated_at", datetime.now(timezone.utc).isoformat()),
        )

    # Write JSONL file
    output_path = write_emus_jsonl(folder, jsonl_emus, output)

    # Write lock file
    meta = LockMeta(
        workspace=ws,
        project=proj,
        pulled_at=datetime.now(timezone.utc).isoformat(),
    )
    write_lock_file(folder, meta, lock_entries)

    console.print(f"[green]Pulled {len(jsonl_emus)} EMUs from {ws}/{proj} to {output_path}[/green]")
    console.print(f"[dim]Lock file: {folder / '.emu.lock.jsonl'}[/dim]")


def emu_plan(
    folder: Path = typer.Argument(
        ".",
        help="Folder containing JSONL files (default: current directory)",
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Override target workspace",
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Override target project",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output as JSON for CI/CD",
    ),
    validate_only: bool = typer.Option(
        False,
        "--validate-only",
        help="Only validate DSL, skip remote diff",
    ),
    validate_server: bool = typer.Option(
        False,
        "--validate", "-V",
        help="Run server-side ASR validation for existing EMUs",
    ),
):
    """
    Show diff between local and remote. Does not modify anything.

    Examples:
        memrail emu-plan ./emus/
        memrail emu-plan ./emus/ --workspace staging --project api
    """
    load_environment()

    # Scan for JSONL files
    jsonl_files = scan_jsonl_files(folder)
    if not jsonl_files:
        console.print(f"[red]Error:[/red] No *.jsonl files found in {folder}")
        raise typer.Exit(1)

    console.print(f"Scanning {folder} for JSONL files...")
    file_summary = ", ".join([f"{f.name}" for f in jsonl_files])
    console.print(f"  Found: {file_summary}")

    # Read local EMUs
    local_emus = read_local_emus(folder)
    console.print(f"  Total: {len(local_emus)} EMUs")

    # Check for duplicates
    check_duplicates(local_emus)

    # Validate DSL
    if not validate_dsl(local_emus):
        raise typer.Exit(1)

    if validate_only:
        console.print(f"\n[green]Validation passed for {len(local_emus)} EMUs[/green]")
        raise typer.Exit(0)

    # Resolve target
    ws, proj, source = resolve_target(folder, workspace, project)
    console.print(f"  Target: workspace={ws}, project={proj} (from {source})")

    # Read lock file
    _, lock_entries = read_lock_file(folder)

    # Fetch remote EMUs
    console.print(f"\n[dim]Fetching remote EMUs...[/dim]")
    client = get_ami_client(workspace=ws, project=proj)
    org = os.environ.get("AMI_ORG")
    team = os.environ.get("AMI_TEAM")

    try:
        result = client.list_emus(org=org, team=team, workspace=ws, project=proj)
        # list_emus returns {"emus": [...], "total": n, ...}
        remote_emus = result.get("emus", []) if isinstance(result, dict) else result
    except Exception as e:
        console.print(f"[red]Error:[/red] Failed to fetch remote EMUs: {e}")
        raise typer.Exit(1)

    remote_emus = dedup_latest_versions(remote_emus)
    remote_by_key = {e.get("emu_key"): e for e in remote_emus}
    local_by_key = {e.emu_key: e for e in local_emus}

    # Compute diff
    to_add = []
    to_modify = []
    to_delete = []
    unchanged = []

    # Check local EMUs
    for emu_key, local_emu in local_by_key.items():
        remote_emu = remote_by_key.get(emu_key)

        if remote_emu is None:
            to_add.append(local_emu)
        else:
            # Check if modified
            local_hash = local_emu.content_hash()
            lock_entry = lock_entries.get(emu_key)

            if lock_entry and lock_entry.content_hash != local_hash:
                to_modify.append((local_emu, remote_emu))
            elif not lock_entry:
                # No lock entry, compare with remote
                # For simplicity, treat as modified if trigger differs
                if local_emu.trigger != remote_emu.get("trigger", ""):
                    to_modify.append((local_emu, remote_emu))
                else:
                    unchanged.append(local_emu)
            else:
                unchanged.append(local_emu)

    # Check for deletions (in remote but not local)
    for emu_key, remote_emu in remote_by_key.items():
        if emu_key not in local_by_key:
            # Only count as deletion if it was in our lock file
            if emu_key in lock_entries:
                to_delete.append(remote_emu)

    # Output
    console.print("\n[bold]Planning EMU changes...[/bold]\n")

    if not to_add and not to_modify and not to_delete:
        console.print("[green]No changes needed. Local and remote are in sync.[/green]")
        if json_output:
            print(json.dumps({"changes": [], "summary": {"add": 0, "modify": 0, "delete": 0}}))
        raise typer.Exit(0)

    console.print("[bold]Changes:[/bold]")

    for emu in to_add:
        console.print(f"  [green]+[/green] {emu.emu_key} (new) [{emu.source_file}:{emu.source_line}]")

    for local_emu, remote_emu in to_modify:
        console.print(f"  [yellow]~[/yellow] {local_emu.emu_key} (modified) [{local_emu.source_file}:{local_emu.source_line}]")
        # Show trigger diff if different
        if local_emu.trigger != remote_emu.get("trigger", ""):
            console.print(f"      [dim]- trigger: was '{remote_emu.get('trigger', '')[:50]}...'[/dim]")
            console.print(f"      [dim]+ trigger: now '{local_emu.trigger[:50]}...'[/dim]")

    for emu in to_delete:
        console.print(f"  [red]-[/red] {emu.get('emu_key')} (delete, not in local files)")

    console.print(f"\n[bold]Summary:[/bold] {len(to_add)} to add, {len(to_modify)} to modify, {len(to_delete)} to delete")

    # Server-side ASR validation for existing EMUs
    validation_data: Dict[str, Any] = {}
    if validate_server:
        # Only validate EMUs that already exist remotely (modified + unchanged)
        existing_keys = [local_emu.emu_key for local_emu, _ in to_modify] + [e.emu_key for e in unchanged]
        if existing_keys:
            console.print(f"\n[bold]Validation (server-side ASR):[/bold]")
            try:
                val_result = client.validate_workspace_emus(workspace=ws)
                reports = val_result.get("reports", [])
                pass_count, warn_count = display_validation_results(reports, existing_keys)
                validation_data = {"pass": pass_count, "warnings": warn_count, "reports": reports}
                console.print(f"\n  Validation: {pass_count} pass, {warn_count} with warnings")
            except Exception as e:
                console.print(f"  [yellow]Validation unavailable:[/yellow] {e}")
        if to_add:
            console.print(f"  [dim]{len(to_add)} new EMU(s) — validation available after apply[/dim]")

    console.print(f"\nRun `memrail emu-apply {folder}` to apply these changes.")

    if json_output:
        changes = []
        for emu in to_add:
            changes.append({"type": "add", "emu_key": emu.emu_key})
        for local_emu, _ in to_modify:
            changes.append({"type": "modify", "emu_key": local_emu.emu_key})
        for emu in to_delete:
            changes.append({"type": "delete", "emu_key": emu.get("emu_key")})

        output = {
            "changes": changes,
            "summary": {"add": len(to_add), "modify": len(to_modify), "delete": len(to_delete)},
        }
        if validation_data:
            output["validation"] = validation_data

        print(json.dumps(output))

    # Normal exit — changes pending is informational, not an error
    return


def emu_apply(
    folder: Path = typer.Argument(
        ".",
        help="Folder containing JSONL files (default: current directory)",
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Override target workspace",
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Override target project",
    ),
    yes: bool = typer.Option(
        False,
        "--yes", "-y",
        help="Skip confirmation prompt",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Validate and show plan, don't apply",
    ),
    filter_pattern: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Only apply matching EMU keys (glob pattern)",
    ),
    target_state: str = typer.Option(
        "draft",
        "--target-state",
        help="Target lifecycle state for new EMUs",
    ),
    hard_delete: bool = typer.Option(
        False,
        "--hard-delete",
        help="Permanently delete instead of archive",
    ),
    validate_server: bool = typer.Option(
        False,
        "--validate", "-V",
        help="Run server-side ASR validation after apply",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output as JSON for CI/CD",
    ),
):
    """
    Push local changes to remote.

    Examples:
        memrail emu-apply ./emus/
        memrail emu-apply ./emus/ --yes
        memrail emu-apply ./emus/ --workspace staging --project api
    """
    import fnmatch

    load_environment()

    # Scan for JSONL files
    jsonl_files = scan_jsonl_files(folder)
    if not jsonl_files:
        console.print(f"[red]Error:[/red] No *.jsonl files found in {folder}")
        raise typer.Exit(1)

    console.print(f"Scanning {folder} for JSONL files...")
    file_summary = ", ".join([f"{f.name}" for f in jsonl_files])
    console.print(f"  Found: {file_summary}")

    # Read local EMUs
    local_emus = read_local_emus(folder)

    # Apply filter
    if filter_pattern:
        local_emus = [e for e in local_emus if fnmatch.fnmatch(e.emu_key, filter_pattern)]

    console.print(f"  Total: {len(local_emus)} EMUs")

    # Check for duplicates
    check_duplicates(local_emus)

    # Validate DSL
    if not validate_dsl(local_emus):
        raise typer.Exit(1)

    # Resolve target
    ws, proj, source = resolve_target(folder, workspace, project)
    console.print(f"  Target: workspace={ws}, project={proj} (from {source})")

    # Read lock file
    meta, lock_entries = read_lock_file(folder)

    # Fetch remote EMUs
    console.print(f"\n[dim]Fetching remote EMUs...[/dim]")
    client = get_ami_client(workspace=ws, project=proj)
    org = os.environ.get("AMI_ORG")
    team = os.environ.get("AMI_TEAM")

    try:
        result = client.list_emus(org=org, team=team, workspace=ws, project=proj)
        # list_emus returns {"emus": [...], "total": n, ...}
        remote_emus = result.get("emus", []) if isinstance(result, dict) else result
    except Exception as e:
        console.print(f"[red]Error:[/red] Failed to fetch remote EMUs: {e}")
        raise typer.Exit(1)

    remote_emus = dedup_latest_versions(remote_emus)
    remote_by_key = {e.get("emu_key"): e for e in remote_emus}
    local_by_key = {e.emu_key: e for e in local_emus}

    # Compute diff
    to_add = []
    to_modify = []
    to_delete = []

    for emu_key, local_emu in local_by_key.items():
        remote_emu = remote_by_key.get(emu_key)

        if remote_emu is None:
            to_add.append(local_emu)
        else:
            local_hash = local_emu.content_hash()
            lock_entry = lock_entries.get(emu_key)

            if lock_entry and lock_entry.content_hash != local_hash:
                to_modify.append((local_emu, remote_emu))
            elif not lock_entry:
                if local_emu.trigger != remote_emu.get("trigger", ""):
                    to_modify.append((local_emu, remote_emu))

    for emu_key, remote_emu in remote_by_key.items():
        if emu_key not in local_by_key and emu_key in lock_entries:
            to_delete.append(remote_emu)

    if not to_add and not to_modify and not to_delete:
        console.print("\n[green]No changes needed. Local and remote are in sync.[/green]")
        raise typer.Exit(0)

    # Show plan
    console.print("\n[bold]Changes to apply:[/bold]")

    for emu in to_add:
        console.print(f"  [green]+[/green] {emu.emu_key} (new) [{emu.source_file}:{emu.source_line}]")

    for local_emu, _ in to_modify:
        console.print(f"  [yellow]~[/yellow] {local_emu.emu_key} (modified) [{local_emu.source_file}:{local_emu.source_line}]")

    for emu in to_delete:
        console.print(f"  [red]-[/red] {emu.get('emu_key')} (archive)")

    console.print(f"\n[bold]Summary:[/bold] {len(to_add)} to add, {len(to_modify)} to modify, {len(to_delete)} to archive")

    if dry_run:
        console.print("\n[dim]Dry run - no changes applied[/dim]")
        raise typer.Exit(0)

    # Confirm
    if not yes:
        confirm = typer.confirm("\nDo you want to apply these changes?")
        if not confirm:
            console.print("[dim]Aborted[/dim]")
            raise typer.Exit(0)

    # Apply changes
    console.print("\n[bold]Applying EMU changes...[/bold]\n")

    success_count = 0
    error_count = 0
    new_lock_entries = dict(lock_entries)

    # Add new EMUs
    for emu in to_add:
        try:
            result = client.register_emu(
                emu_key=emu.emu_key,
                trigger=emu.trigger,
                action=emu.action,
                expected_utility=emu.expected_utility,
                confidence=emu.confidence,
                policy=emu.policy,
                intent=emu.intent,
                metadata=emu.metadata,
                state=target_state,
                workspace=ws,
                project=proj,
            )
            # API returns {"status": "created", "emu": {emu_id, version, state, ...}}
            emu_data = result.get("emu", {}) if isinstance(result.get("emu"), dict) else {}
            if not emu_data:
                console.print(f"  [yellow]warn:[/yellow] {emu.emu_key}: unexpected API response shape, using fallback values")
            version = emu_data.get("version") or result.get("version", 1)
            emu_id = emu_data.get("emu_id") or result.get("emu_id", "")
            console.print(f"  [green]+[/green] {emu.emu_key} ... OK (v{version}, {target_state})")

            new_lock_entries[emu.emu_key] = LockEntry(
                emu_key=emu.emu_key,
                version=version,
                emu_id=emu_id,
                state=target_state,
                content_hash=emu.content_hash(),
                deployed_at=datetime.now(timezone.utc).isoformat(),
            )
            success_count += 1
        except Exception as e:
            console.print(f"  [red]+[/red] {emu.emu_key} ... FAILED: {e}")
            error_count += 1

    # Modify existing EMUs
    for local_emu, remote_emu in to_modify:
        try:
            result = client.update_emu(
                emu_key=local_emu.emu_key,
                trigger=local_emu.trigger,
                action=local_emu.action,
                expected_utility=local_emu.expected_utility,
                confidence=local_emu.confidence,
                policy=local_emu.policy,
                intent=local_emu.intent,
                metadata=local_emu.metadata,
                workspace=ws,
                project=proj,
            )

            # Handle skip case: API returns {"status": "skipped", "emu_key": ..., "message": ...}
            # when content is identical (no "emu" key in response)
            if result.get("status") == "skipped":
                version = remote_emu.get("version", 1)
                state = remote_emu.get("state", "active")
                console.print(f"  [dim]=[/dim] {local_emu.emu_key} ... skipped (v{version}, {state}, content identical)")
                # Keep existing lock entry unchanged
                if local_emu.emu_key in lock_entries:
                    new_lock_entries[local_emu.emu_key] = lock_entries[local_emu.emu_key]
                success_count += 1
                continue

            # API returns {"status": "updated", "emu": {emu_id, version, state, ...}, "message": "..."}
            emu_data = result.get("emu", {}) if isinstance(result.get("emu"), dict) else {}
            if not emu_data:
                console.print(
                    f"  [yellow]warn:[/yellow] {local_emu.emu_key}: unexpected API response shape, "
                    f"state/version from remote may be stale"
                )
            version = emu_data.get("version") or result.get("version") or remote_emu.get("version", 1) + 1
            state = emu_data.get("state") or result.get("state") or remote_emu.get("state", "active")
            emu_id = emu_data.get("emu_id") or result.get("emu_id") or remote_emu.get("emu_id", "")
            console.print(f"  [yellow]~[/yellow] {local_emu.emu_key} ... OK (v{version}, {state})")

            new_lock_entries[local_emu.emu_key] = LockEntry(
                emu_key=local_emu.emu_key,
                version=version,
                emu_id=emu_id,
                state=state,
                content_hash=local_emu.content_hash(),
                deployed_at=datetime.now(timezone.utc).isoformat(),
            )
            success_count += 1
        except Exception as e:
            console.print(f"  [red]~[/red] {local_emu.emu_key} ... FAILED: {e}")
            error_count += 1

    # Archive deleted EMUs
    for emu in to_delete:
        emu_key = emu.get("emu_key")
        try:
            client.update_emu_lifecycle(
                emu_key=emu_key,
                state="archived",
                workspace=ws,
                project=proj,
            )
            console.print(f"  [red]-[/red] {emu_key} ... OK (archived)")

            # Remove from lock entries
            if emu_key in new_lock_entries:
                del new_lock_entries[emu_key]
            success_count += 1
        except Exception as e:
            console.print(f"  [red]-[/red] {emu_key} ... FAILED: {e}")
            error_count += 1

    # Update lock file
    new_meta = LockMeta(
        workspace=ws,
        project=proj,
        pulled_at=datetime.now(timezone.utc).isoformat(),
    )
    write_lock_file(folder, new_meta, new_lock_entries)

    console.print(f"\n[bold]Summary:[/bold] {success_count} succeeded, {error_count} failed")

    # Server-side ASR validation after apply
    validation_json: Dict[str, Any] = {}
    if validate_server and success_count > 0:
        # Validate all added + modified EMUs (skip archived)
        applied_keys = [e.emu_key for e in to_add] + [le.emu_key for le, _ in to_modify]
        applied_keys = [k for k in applied_keys if k in new_lock_entries]  # only successful ones

        if applied_keys:
            console.print(f"\n[bold]Validation (server-side ASR):[/bold]")
            try:
                val_result = client.validate_workspace_emus(workspace=ws)
                reports = val_result.get("reports", [])
                pass_count, warn_count = display_validation_results(reports, applied_keys)
                validation_json = {"pass": pass_count, "warnings": warn_count}

                if json_output:
                    reports_by_key = {r.get("emu_key"): r for r in reports}
                    validation_json["reports"] = [
                        reports_by_key[k] for k in applied_keys if k in reports_by_key
                    ]

                console.print(f"\n  Validation: {pass_count} pass, {warn_count} with warnings")
            except Exception as e:
                console.print(f"  [yellow]Validation unavailable:[/yellow] {e}")

    console.print(f"[dim]Lock file updated: {folder / '.emu.lock.jsonl'}[/dim]")

    if json_output:
        output: Dict[str, Any] = {
            "summary": {"succeeded": success_count, "failed": error_count},
        }
        if validation_json:
            output["validation"] = validation_json
        print(json.dumps(output))

    if error_count > 0:
        raise typer.Exit(1)


def emu_diff(
    folder: Path = typer.Argument(
        ".",
        help="Folder containing JSONL files (default: current directory)",
    ),
    emu_key: str = typer.Argument(
        ...,
        help="EMU key to diff",
    ),
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Override target workspace",
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Override target project",
    ),
):
    """
    Show detailed diff for a specific EMU.

    Examples:
        memrail emu-diff ./emus/ vip_escalation
        memrail emu-diff ./emus/ vip_escalation -w staging -p api
    """
    load_environment()

    # Read local EMUs
    local_emus = read_local_emus(folder)
    local_by_key = {e.emu_key: e for e in local_emus}

    local_emu = local_by_key.get(emu_key)

    # Resolve target
    ws, proj, source = resolve_target(folder, workspace, project)

    console.print(f"[bold]EMU:[/bold] {emu_key}")
    console.print(f"[dim]Target: workspace={ws}, project={proj} (from {source})[/dim]\n")

    # Fetch remote EMU
    client = get_ami_client(workspace=ws, project=proj)

    try:
        remote_emu = client.get_emu(emu_key=emu_key, workspace=ws, project=proj)
    except Exception:
        remote_emu = None

    # Display diff
    if local_emu:
        console.print(f"[bold green]Local [{local_emu.source_file}:{local_emu.source_line}]:[/bold green]")
        console.print(f"  trigger: {local_emu.trigger}")
        console.print(f"  action.type: {local_emu.action.get('type', '?')}")
        if local_emu.action.get("tool"):
            console.print(f"  action.tool: {local_emu.action.get('tool')}")
        console.print(f"  expected_utility: {local_emu.expected_utility}")
        console.print(f"  confidence: {local_emu.confidence}")
        if local_emu.policy:
            console.print(f"  policy: {json.dumps(local_emu.policy)}")
    else:
        console.print(f"[yellow]Local: Not found[/yellow]")

    console.print()

    if remote_emu:
        version = remote_emu.get("version", "?")
        state = remote_emu.get("state", "?")
        console.print(f"[bold blue]Remote (v{version}, {state}):[/bold blue]")
        console.print(f"  trigger: {remote_emu.get('trigger', '?')}")
        action = remote_emu.get("action", {})
        console.print(f"  action.type: {action.get('type', '?')}")
        if action.get("tool"):
            console.print(f"  action.tool: {action.get('tool')}")
        console.print(f"  expected_utility: {remote_emu.get('expected_utility', '?')}")
        console.print(f"  confidence: {remote_emu.get('confidence', '?')}")
        policy = remote_emu.get("policy")
        if policy:
            console.print(f"  policy: {json.dumps(policy)}")
    else:
        console.print(f"[yellow]Remote: Not found[/yellow]")

    # Show changes
    if local_emu and remote_emu:
        console.print("\n[bold]Changes:[/bold]")
        changes = []

        if local_emu.trigger != remote_emu.get("trigger", ""):
            changes.append(f"  [yellow]~[/yellow] trigger")
        if local_emu.action != remote_emu.get("action", {}):
            changes.append(f"  [yellow]~[/yellow] action")
        if local_emu.expected_utility != remote_emu.get("expected_utility"):
            changes.append(f"  [yellow]~[/yellow] expected_utility: {remote_emu.get('expected_utility')} -> {local_emu.expected_utility}")
        if local_emu.confidence != remote_emu.get("confidence"):
            changes.append(f"  [yellow]~[/yellow] confidence: {remote_emu.get('confidence')} -> {local_emu.confidence}")
        if local_emu.policy != (remote_emu.get("policy") or {}):
            changes.append(f"  [yellow]~[/yellow] policy")

        if changes:
            for change in changes:
                console.print(change)
        else:
            console.print("  [green]No changes[/green]")
    elif local_emu and not remote_emu:
        console.print("\n[green]This EMU will be created (new)[/green]")
    elif remote_emu and not local_emu:
        console.print("\n[red]This EMU will be archived (deleted locally)[/red]")


def emu_validate(
    workspace: Optional[str] = typer.Option(
        None,
        "--workspace", "-w",
        help="Workspace to validate (or set AMI_WORKSPACE env var)",
    ),
    project: Optional[str] = typer.Option(
        None,
        "--project", "-p",
        help="Project to filter (optional, validates all EMUs in workspace)",
    ),
    filter_pattern: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Glob pattern to filter EMU keys (e.g., 'support.*')",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output as JSON for CI/CD",
    ),
):
    """
    Validate all EMUs in a workspace using server-side ASR checks.

    Returns exit code 1 if any EMU has severity=error warnings, 0 otherwise.

    Examples:
        memrail emu-validate -w production -p my-project
        memrail emu-validate -w production --json
        memrail emu-validate -w production --filter "support.*"
    """
    import fnmatch

    load_environment()

    ws = workspace or os.environ.get("AMI_WORKSPACE")
    if not ws:
        console.print(
            "[red]Error:[/red] --workspace/-w is required "
            "(or set AMI_WORKSPACE environment variable)"
        )
        raise typer.Exit(1)

    console.print(f"[dim]Validating EMUs in workspace={ws}...[/dim]")

    client = get_ami_client(workspace=ws, project=project)

    try:
        result = client.validate_workspace_emus(workspace=ws)
    except Exception as e:
        console.print(f"[red]Error:[/red] Validation request failed: {e}")
        raise typer.Exit(1)

    reports = result.get("reports", [])
    total = result.get("total", len(reports))

    # Filter by pattern
    if filter_pattern:
        reports = [
            r for r in reports
            if fnmatch.fnmatch(r.get("emu_key", ""), filter_pattern)
        ]

    if not reports:
        console.print("[yellow]No EMUs found matching criteria[/yellow]")
        if json_output:
            print(json.dumps({"total": 0, "reports": [], "passed": 0, "warnings": 0, "errors": 0}))
        raise typer.Exit(0)

    # Display results
    pass_count = 0
    warn_count = 0
    error_count = 0
    json_reports = []

    for item in reports:
        emu_key = item.get("emu_key", "?")
        emu_state = item.get("state", "?")
        report = item.get("report", {})
        passed = report.get("passed", True)
        warnings = report.get("warnings", [])
        reachability = report.get("reachability", {})
        has_errors = any(w.get("severity") == "error" for w in warnings)

        if passed and not warnings:
            pass_count += 1
            if not json_output:
                total_deps = reachability.get("total_dependencies", 0)
                reachable = reachability.get("reachable_dependencies", 0)
                console.print(
                    f"  [green]\u2713[/green] {emu_key} ({emu_state}) — "
                    f"{reachable}/{total_deps} dependencies reachable"
                )
        else:
            if has_errors:
                error_count += 1
            else:
                warn_count += 1
            if not json_output:
                status_icon = "[red]\u2717[/red]" if has_errors else "[yellow]\u26a0[/yellow]"
                console.print(f"  {status_icon} {emu_key} ({emu_state})")
                for w in warnings:
                    severity = w.get("severity", "warning")
                    code = w.get("code", "UNKNOWN")
                    message = w.get("message", "")
                    color = "red" if severity == "error" else "yellow"
                    console.print(f"    [{color}]{code}:[/{color}] {message}")
                    remediation = w.get("remediation")
                    if remediation:
                        console.print(f"      [dim]Fix: {remediation}[/dim]")

        if json_output:
            json_reports.append({
                "emu_key": emu_key,
                "state": emu_state,
                "passed": passed,
                "warnings": warnings,
                "reachability": reachability,
            })

    if not json_output:
        console.print(
            f"\n[bold]Summary:[/bold] {len(reports)} EMUs validated — "
            f"{pass_count} pass, {warn_count} warnings, {error_count} errors"
        )

    if json_output:
        print(json.dumps({
            "total": len(reports),
            "passed": pass_count,
            "warnings": warn_count,
            "errors": error_count,
            "reports": json_reports,
        }))

    # Exit code 1 if any EMU has severity=error
    if error_count > 0:
        raise typer.Exit(1)
