#!/usr/bin/env python3
"""
Time Metrics Module for Luzia Task Measurement & Reporting

Provides:
- UTC timestamp generation for task dispatch/completion
- Duration calculations
- System context snapshots (CPU, memory, disk)
- Timezone conversion support
- Causality tracking between tasks
- Performance baseline and anomaly detection

Uses Time MCP for accurate timezone-aware timestamps.
"""

import json
import os
import subprocess
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, Any, Optional, Tuple, List
import sqlite3

# Default timezone for display
DEFAULT_TIMEZONE = "America/Montevideo"


# =============================================================================
# TIMESTAMP FUNCTIONS
# =============================================================================

def get_utc_now() -> str:
    """Get current UTC timestamp in ISO 8601 format.

    Returns format: 2026-01-11T03:31:57Z
    """
    return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")


def get_utc_now_with_offset() -> str:
    """Get current UTC timestamp with +00:00 offset.

    Returns format: 2026-01-11T03:31:57+00:00
    """
    return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S+00:00")


def parse_iso_timestamp(ts: str) -> Optional[datetime]:
    """Parse ISO 8601 timestamp to datetime object.

    Handles formats:
    - 2026-01-11T03:31:57Z
    - 2026-01-11T03:31:57+00:00
    - 2026-01-11T03:31:57-03:00
    - 2026-01-11T03:31:57 (assumes UTC)
    """
    if not ts:
        return None

    try:
        # Remove Z suffix and treat as UTC
        if ts.endswith('Z'):
            ts = ts[:-1]

        # Handle timezone offset
        if '+' in ts or (ts.count('-') > 2):
            # Has timezone - use fromisoformat
            return datetime.fromisoformat(ts)
        else:
            # Assume UTC
            return datetime.fromisoformat(ts)
    except (ValueError, TypeError):
        return None


def calculate_duration_seconds(start_ts: str, end_ts: str) -> Optional[float]:
    """Calculate duration between two ISO timestamps in seconds."""
    start = parse_iso_timestamp(start_ts)
    end = parse_iso_timestamp(end_ts)

    if start is None or end is None:
        return None

    return (end - start).total_seconds()


def format_duration(seconds: Optional[float]) -> str:
    """Format duration in HH:MM:SS format.

    Examples:
    - 3661 -> "01:01:01"
    - 45 -> "00:00:45"
    - 3700 -> "01:01:40"
    """
    if seconds is None or seconds < 0:
        return "--:--:--"

    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)

    return f"{hours:02d}:{minutes:02d}:{secs:02d}"


def format_duration_human(seconds: Optional[float]) -> str:
    """Format duration in human-readable format.

    Examples:
    - 45 -> "45s"
    - 125 -> "2m 5s"
    - 3661 -> "1h 1m 1s"
    - 90061 -> "1d 1h 1m"
    """
    if seconds is None or seconds < 0:
        return "unknown"

    if seconds < 60:
        return f"{int(seconds)}s"
    elif seconds < 3600:
        mins = int(seconds // 60)
        secs = int(seconds % 60)
        return f"{mins}m {secs}s"
    elif seconds < 86400:
        hours = int(seconds // 3600)
        mins = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        return f"{hours}h {mins}m {secs}s"
    else:
        days = int(seconds // 86400)
        hours = int((seconds % 86400) // 3600)
        mins = int((seconds % 3600) // 60)
        return f"{days}d {hours}h {mins}m"


def elapsed_since(start_ts: str) -> str:
    """Get human-readable elapsed time since a timestamp."""
    now = get_utc_now()
    seconds = calculate_duration_seconds(start_ts, now)
    return format_duration_human(seconds)


# =============================================================================
# TIMEZONE CONVERSION
# =============================================================================

def convert_to_local_time(utc_ts: str, local_tz: str = DEFAULT_TIMEZONE) -> str:
    """Convert UTC timestamp to local timezone display string.

    Returns: "HH:MM:SS" in local timezone
    """
    dt = parse_iso_timestamp(utc_ts)
    if dt is None:
        return utc_ts

    # Get offset for the timezone (simplified approach - use pytz if available)
    try:
        import pytz
        utc = pytz.UTC
        local = pytz.timezone(local_tz)
        dt_utc = utc.localize(dt.replace(tzinfo=None) if dt.tzinfo else dt)
        dt_local = dt_utc.astimezone(local)
        return dt_local.strftime("%H:%M:%S")
    except ImportError:
        # Fallback: for America/Montevideo, offset is -03:00
        if local_tz == "America/Montevideo":
            local_dt = dt - timedelta(hours=3)
            return local_dt.strftime("%H:%M:%S")
        return dt.strftime("%H:%M:%S") + " UTC"


def format_timestamp_with_local(utc_ts: str, local_tz: str = DEFAULT_TIMEZONE) -> str:
    """Format timestamp showing both UTC and local time.

    Returns: "2026-01-11T03:31:57Z (America/Montevideo: 00:31:57)"
    """
    local_time = convert_to_local_time(utc_ts, local_tz)
    return f"{utc_ts} ({local_tz}: {local_time})"


# =============================================================================
# SYSTEM CONTEXT CAPTURE
# =============================================================================

def get_system_load() -> Tuple[float, float, float]:
    """Get system load averages (1, 5, 15 minute)."""
    try:
        return os.getloadavg()
    except OSError:
        return (0.0, 0.0, 0.0)


def get_memory_usage() -> Dict[str, Any]:
    """Get memory usage from /proc/meminfo."""
    try:
        with open("/proc/meminfo") as f:
            lines = f.readlines()

        mem = {}
        for line in lines:
            parts = line.split()
            if len(parts) >= 2:
                key = parts[0].rstrip(":")
                value = int(parts[1])  # kB
                mem[key] = value

        total_mb = mem.get("MemTotal", 0) // 1024
        available_mb = mem.get("MemAvailable", mem.get("MemFree", 0)) // 1024
        used_mb = total_mb - available_mb
        used_pct = int(100 * used_mb / total_mb) if total_mb else 0

        return {
            "total_mb": total_mb,
            "available_mb": available_mb,
            "used_mb": used_mb,
            "used_percent": used_pct
        }
    except Exception:
        return {
            "total_mb": 0,
            "available_mb": 0,
            "used_mb": 0,
            "used_percent": 0
        }


def get_disk_usage(path: str = "/") -> Dict[str, Any]:
    """Get disk usage for a path."""
    try:
        stat = os.statvfs(path)
        total = stat.f_blocks * stat.f_frsize
        free = stat.f_bavail * stat.f_frsize
        used = total - free
        used_pct = int(100 * used / total) if total else 0

        return {
            "total_gb": round(total / (1024**3), 1),
            "free_gb": round(free / (1024**3), 1),
            "used_gb": round(used / (1024**3), 1),
            "used_percent": used_pct
        }
    except Exception:
        return {
            "total_gb": 0,
            "free_gb": 0,
            "used_gb": 0,
            "used_percent": 0
        }


def capture_system_context() -> Dict[str, Any]:
    """Capture full system context snapshot.

    Returns dict suitable for storing in task metadata.
    """
    load = get_system_load()
    memory = get_memory_usage()
    disk = get_disk_usage("/")

    return {
        "timestamp": get_utc_now(),
        "system_load": [round(l, 2) for l in load],
        "memory": {
            "used_percent": memory["used_percent"],
            "available_mb": memory["available_mb"]
        },
        "disk": {
            "used_percent": disk["used_percent"],
            "free_gb": disk["free_gb"]
        }
    }


# =============================================================================
# TASK TIME METADATA
# =============================================================================

class TaskTimeTracker:
    """Track time metrics for a task throughout its lifecycle."""

    def __init__(self, task_id: str, project: str):
        self.task_id = task_id
        self.project = project
        self.dispatch_time: Optional[str] = None
        self.start_time: Optional[str] = None
        self.completion_time: Optional[str] = None
        self.dispatch_context: Optional[Dict] = None
        self.completion_context: Optional[Dict] = None
        self.agent_timezone: str = DEFAULT_TIMEZONE

    def mark_dispatched(self) -> Dict[str, Any]:
        """Mark task as dispatched, capturing context."""
        self.dispatch_time = get_utc_now()
        self.dispatch_context = capture_system_context()

        return {
            "dispatch": {
                "utc_time": self.dispatch_time,
                "agent_timezone": self.agent_timezone,
                "system_load": self.dispatch_context["system_load"],
                "memory_percent": self.dispatch_context["memory"]["used_percent"],
                "disk_percent": self.dispatch_context["disk"]["used_percent"]
            }
        }

    def mark_started(self) -> Dict[str, Any]:
        """Mark task as started (agent begins execution)."""
        self.start_time = get_utc_now()

        return {
            "start_time": self.start_time
        }

    def mark_completed(self, exit_code: int = 0) -> Dict[str, Any]:
        """Mark task as completed, capturing final context."""
        self.completion_time = get_utc_now()
        self.completion_context = capture_system_context()

        duration = calculate_duration_seconds(
            self.dispatch_time or self.start_time or self.completion_time,
            self.completion_time
        )

        return {
            "completion": {
                "utc_time": self.completion_time,
                "duration_seconds": duration,
                "duration_formatted": format_duration(duration),
                "exit_code": exit_code,
                "system_load": self.completion_context["system_load"],
                "memory_percent": self.completion_context["memory"]["used_percent"],
                "disk_percent": self.completion_context["disk"]["used_percent"]
            }
        }

    def get_full_metrics(self) -> Dict[str, Any]:
        """Get complete time metrics for the task."""
        metrics = {
            "task_id": self.task_id,
            "project": self.project,
            "agent_timezone": self.agent_timezone
        }

        if self.dispatch_time:
            metrics["dispatch"] = {
                "utc_time": self.dispatch_time,
                "local_time": convert_to_local_time(self.dispatch_time, self.agent_timezone),
                "context": self.dispatch_context
            }

        if self.start_time:
            metrics["start_time"] = self.start_time

        if self.completion_time:
            duration = calculate_duration_seconds(
                self.dispatch_time or self.start_time,
                self.completion_time
            )
            metrics["completion"] = {
                "utc_time": self.completion_time,
                "local_time": convert_to_local_time(self.completion_time, self.agent_timezone),
                "duration_seconds": duration,
                "duration_formatted": format_duration(duration),
                "context": self.completion_context
            }

        # Calculate elapsed if still running
        if self.dispatch_time and not self.completion_time:
            metrics["elapsed"] = elapsed_since(self.dispatch_time)
            metrics["status"] = "running"
        elif self.completion_time:
            metrics["status"] = "completed"
        else:
            metrics["status"] = "pending"

        return metrics


# =============================================================================
# CAUSALITY TRACKING
# =============================================================================

def get_recent_completions(limit: int = 10) -> List[Dict[str, Any]]:
    """Get recently completed tasks for causality tracking.

    Returns list of {task_id, project, completion_time} sorted by time.
    """
    jobs_dir = Path("/var/log/luz-orchestrator/jobs")
    completions = []

    if not jobs_dir.exists():
        return completions

    for job_dir in jobs_dir.iterdir():
        if not job_dir.is_dir():
            continue

        meta_file = job_dir / "meta.json"
        if not meta_file.exists():
            continue

        try:
            with open(meta_file) as f:
                meta = json.load(f)

            # Check for time_metrics with completion
            time_metrics = meta.get("time_metrics", {})
            completion = time_metrics.get("completion", {})

            if completion.get("utc_time"):
                completions.append({
                    "task_id": meta.get("id", job_dir.name),
                    "project": meta.get("project", "unknown"),
                    "completion_time": completion["utc_time"],
                    "duration_seconds": completion.get("duration_seconds")
                })
        except Exception:
            continue

    # Sort by completion time, newest first
    completions.sort(key=lambda x: x["completion_time"], reverse=True)
    return completions[:limit]


def find_prior_task(dispatch_time: str, project: str = None) -> Optional[Dict[str, Any]]:
    """Find the most recent task that completed before this dispatch.

    Useful for causality tracking: "This task started 5min after Task X completed"
    """
    recent = get_recent_completions(limit=20)

    for task in recent:
        # Skip if filtering by project and doesn't match
        if project and task["project"] != project:
            continue

        # Check if this task completed before our dispatch
        if task["completion_time"] < dispatch_time:
            # Calculate gap
            gap_seconds = calculate_duration_seconds(task["completion_time"], dispatch_time)
            return {
                **task,
                "gap_seconds": gap_seconds,
                "gap_formatted": format_duration_human(gap_seconds)
            }

    return None


# =============================================================================
# PERFORMANCE BASELINE & ANOMALY DETECTION
# =============================================================================

# Performance baseline database
BASELINE_DB = Path("/var/lib/luzia/metrics/baselines.db")


def init_baseline_db():
    """Initialize the performance baseline database."""
    BASELINE_DB.parent.mkdir(parents=True, exist_ok=True)

    conn = sqlite3.connect(BASELINE_DB)
    c = conn.cursor()

    # Task durations by project
    c.execute('''CREATE TABLE IF NOT EXISTS task_durations (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        project TEXT NOT NULL,
        task_hash TEXT,
        duration_seconds REAL NOT NULL,
        exit_code INTEGER,
        system_load_avg REAL,
        memory_percent INTEGER,
        recorded_at TEXT NOT NULL
    )''')

    # Baseline statistics (calculated aggregates)
    c.execute('''CREATE TABLE IF NOT EXISTS baselines (
        project TEXT PRIMARY KEY,
        avg_duration REAL,
        median_duration REAL,
        stddev_duration REAL,
        p95_duration REAL,
        sample_count INTEGER,
        updated_at TEXT
    )''')

    c.execute('CREATE INDEX IF NOT EXISTS idx_durations_project ON task_durations(project)')
    c.execute('CREATE INDEX IF NOT EXISTS idx_durations_recorded ON task_durations(recorded_at)')

    conn.commit()
    return conn


def record_task_duration(project: str, duration_seconds: float,
                         exit_code: int = 0, task_hash: str = None,
                         system_load: float = None, memory_percent: int = None):
    """Record a task duration for baseline calculation."""
    conn = init_baseline_db()
    c = conn.cursor()

    c.execute('''INSERT INTO task_durations
                 (project, task_hash, duration_seconds, exit_code,
                  system_load_avg, memory_percent, recorded_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?)''',
              (project, task_hash, duration_seconds, exit_code,
               system_load, memory_percent, get_utc_now()))

    conn.commit()
    conn.close()


def calculate_baseline(project: str) -> Dict[str, Any]:
    """Calculate performance baseline for a project.

    Uses last 30 days of successful tasks.
    """
    conn = init_baseline_db()
    c = conn.cursor()

    # Get durations from last 30 days, exit_code 0 only
    cutoff = (datetime.utcnow() - timedelta(days=30)).strftime("%Y-%m-%dT%H:%M:%SZ")

    c.execute('''SELECT duration_seconds FROM task_durations
                 WHERE project = ? AND exit_code = 0 AND recorded_at > ?
                 ORDER BY duration_seconds''',
              (project, cutoff))

    durations = [row[0] for row in c.fetchall()]

    if not durations:
        conn.close()
        return {
            "project": project,
            "sample_count": 0,
            "error": "No data available"
        }

    # Calculate statistics
    n = len(durations)
    avg = sum(durations) / n

    # Median
    mid = n // 2
    median = durations[mid] if n % 2 else (durations[mid-1] + durations[mid]) / 2

    # Standard deviation
    variance = sum((d - avg) ** 2 for d in durations) / n
    stddev = variance ** 0.5

    # 95th percentile
    p95_idx = int(n * 0.95)
    p95 = durations[min(p95_idx, n - 1)]

    baseline = {
        "project": project,
        "avg_duration": round(avg, 2),
        "median_duration": round(median, 2),
        "stddev_duration": round(stddev, 2),
        "p95_duration": round(p95, 2),
        "sample_count": n,
        "updated_at": get_utc_now()
    }

    # Store baseline
    c.execute('''INSERT OR REPLACE INTO baselines
                 (project, avg_duration, median_duration, stddev_duration,
                  p95_duration, sample_count, updated_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?)''',
              (project, baseline["avg_duration"], baseline["median_duration"],
               baseline["stddev_duration"], baseline["p95_duration"],
               baseline["sample_count"], baseline["updated_at"]))

    conn.commit()
    conn.close()

    return baseline


def check_anomaly(project: str, duration_seconds: float) -> Dict[str, Any]:
    """Check if a task duration is anomalous compared to baseline.

    Returns:
        {
            "is_anomaly": bool,
            "severity": "normal"|"slow"|"very_slow"|"extreme",
            "ratio": float (duration / avg),
            "message": str
        }
    """
    conn = init_baseline_db()
    c = conn.cursor()

    c.execute('''SELECT avg_duration, stddev_duration, p95_duration, sample_count
                 FROM baselines WHERE project = ?''', (project,))
    row = c.fetchone()
    conn.close()

    if not row or row[3] < 5:  # Need at least 5 samples
        return {
            "is_anomaly": False,
            "severity": "unknown",
            "ratio": 0,
            "message": "Insufficient baseline data"
        }

    avg, stddev, p95, sample_count = row

    if avg <= 0:
        return {
            "is_anomaly": False,
            "severity": "unknown",
            "ratio": 0,
            "message": "Invalid baseline"
        }

    ratio = duration_seconds / avg

    # Classify severity
    if duration_seconds <= p95:
        severity = "normal"
        is_anomaly = False
        message = "Within normal range"
    elif duration_seconds <= avg + 2 * stddev:
        severity = "slow"
        is_anomaly = True
        message = f"Slower than average ({ratio:.1f}x normal)"
    elif duration_seconds <= avg + 3 * stddev:
        severity = "very_slow"
        is_anomaly = True
        message = f"Much slower than average ({ratio:.1f}x normal)"
    else:
        severity = "extreme"
        is_anomaly = True
        message = f"Extremely slow ({ratio:.1f}x normal)"

    return {
        "is_anomaly": is_anomaly,
        "severity": severity,
        "ratio": round(ratio, 2),
        "message": message,
        "baseline_avg": round(avg, 2),
        "baseline_p95": round(p95, 2)
    }


# =============================================================================
# AGGREGATE METRICS
# =============================================================================

def get_project_metrics(project: str, days: int = 7) -> Dict[str, Any]:
    """Get aggregate metrics for a project over a time period."""
    conn = init_baseline_db()
    c = conn.cursor()

    cutoff = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ")

    c.execute('''SELECT
                    COUNT(*) as total_tasks,
                    SUM(duration_seconds) as total_time,
                    AVG(duration_seconds) as avg_duration,
                    MIN(duration_seconds) as min_duration,
                    MAX(duration_seconds) as max_duration,
                    SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) as success_count,
                    AVG(system_load_avg) as avg_load,
                    AVG(memory_percent) as avg_memory
                 FROM task_durations
                 WHERE project = ? AND recorded_at > ?''',
              (project, cutoff))

    row = c.fetchone()
    conn.close()

    if not row or row[0] == 0:
        return {
            "project": project,
            "period_days": days,
            "total_tasks": 0,
            "error": "No data in period"
        }

    total, total_time, avg, min_dur, max_dur, success, avg_load, avg_mem = row

    return {
        "project": project,
        "period_days": days,
        "total_tasks": total,
        "total_time_seconds": total_time or 0,
        "total_time_formatted": format_duration(total_time),
        "avg_duration_seconds": round(avg or 0, 2),
        "avg_duration_formatted": format_duration(avg),
        "min_duration_seconds": round(min_dur or 0, 2),
        "max_duration_seconds": round(max_dur or 0, 2),
        "success_rate": round(100 * success / total, 1) if total else 0,
        "avg_system_load": round(avg_load or 0, 2),
        "avg_memory_percent": round(avg_mem or 0, 1)
    }


def get_all_projects_metrics(days: int = 7) -> Dict[str, Any]:
    """Get aggregate metrics for all projects."""
    conn = init_baseline_db()
    c = conn.cursor()

    cutoff = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT%H:%M:%SZ")

    # Get per-project stats
    c.execute('''SELECT
                    project,
                    COUNT(*) as total_tasks,
                    SUM(duration_seconds) as total_time,
                    AVG(duration_seconds) as avg_duration,
                    SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) as success_count
                 FROM task_durations
                 WHERE recorded_at > ?
                 GROUP BY project
                 ORDER BY total_time DESC''',
              (cutoff,))

    by_project = {}
    total_tasks = 0
    total_time = 0

    for row in c.fetchall():
        project, tasks, time_sec, avg, success = row
        by_project[project] = {
            "total_tasks": tasks,
            "total_time_seconds": time_sec or 0,
            "total_time_formatted": format_duration(time_sec),
            "avg_duration_seconds": round(avg or 0, 2),
            "success_rate": round(100 * success / tasks, 1) if tasks else 0
        }
        total_tasks += tasks
        total_time += (time_sec or 0)

    # Get longest running tasks
    c.execute('''SELECT project, task_hash, duration_seconds, recorded_at
                 FROM task_durations
                 WHERE recorded_at > ?
                 ORDER BY duration_seconds DESC
                 LIMIT 10''',
              (cutoff,))

    longest_tasks = []
    for row in c.fetchall():
        longest_tasks.append({
            "project": row[0],
            "task_hash": row[1],
            "duration_seconds": row[2],
            "duration_formatted": format_duration(row[2]),
            "recorded_at": row[3]
        })

    conn.close()

    return {
        "period_days": days,
        "total_tasks": total_tasks,
        "total_time_seconds": total_time,
        "total_time_formatted": format_duration(total_time),
        "by_project": by_project,
        "longest_tasks": longest_tasks
    }


# =============================================================================
# TIME-BUCKETED SUCCESS RATES
# =============================================================================

def get_success_by_duration_bucket(project: str = None) -> Dict[str, Any]:
    """Get task success rate by execution time bucket.

    Buckets: <1min, 1-5min, 5-15min, 15-30min, 30-60min, >60min
    """
    conn = init_baseline_db()
    c = conn.cursor()

    buckets = [
        ("under_1m", 0, 60),
        ("1_to_5m", 60, 300),
        ("5_to_15m", 300, 900),
        ("15_to_30m", 900, 1800),
        ("30_to_60m", 1800, 3600),
        ("over_60m", 3600, float('inf'))
    ]

    results = {}

    for bucket_name, min_dur, max_dur in buckets:
        if project:
            c.execute('''SELECT
                            COUNT(*) as total,
                            SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) as success
                         FROM task_durations
                         WHERE project = ? AND duration_seconds >= ? AND duration_seconds < ?''',
                      (project, min_dur, max_dur if max_dur != float('inf') else 1e10))
        else:
            c.execute('''SELECT
                            COUNT(*) as total,
                            SUM(CASE WHEN exit_code = 0 THEN 1 ELSE 0 END) as success
                         FROM task_durations
                         WHERE duration_seconds >= ? AND duration_seconds < ?''',
                      (min_dur, max_dur if max_dur != float('inf') else 1e10))

        row = c.fetchone()
        total, success = row[0] or 0, row[1] or 0

        results[bucket_name] = {
            "total": total,
            "success": success,
            "success_rate": round(100 * success / total, 1) if total else 0
        }

    conn.close()
    return results


# =============================================================================
# DISPLAY FORMATTERS FOR LUZIA
# =============================================================================

def format_job_with_timing(job: Dict[str, Any]) -> str:
    """Format a job entry for 'luzia jobs' output with timing info.

    Returns: "agent:dss:002746 | dss | in_progress | 14:30:00 UTC | 00:45:30 | CPU: 0.52"
    """
    job_id = job.get("id", "unknown")
    project = job.get("project", "unknown")
    status = job.get("status", "unknown")

    time_metrics = job.get("time_metrics", {})
    dispatch = time_metrics.get("dispatch", {})
    completion = time_metrics.get("completion", {})

    # Dispatch time
    dispatch_time = dispatch.get("utc_time", job.get("started", ""))
    if dispatch_time:
        dispatch_display = dispatch_time[11:19]  # HH:MM:SS from ISO
    else:
        dispatch_display = "--:--:--"

    # Duration
    if completion.get("duration_formatted"):
        duration = completion["duration_formatted"]
    elif dispatch_time:
        duration = elapsed_since(dispatch_time)
    else:
        duration = "--:--:--"

    # System load
    load = dispatch.get("system_load", [0, 0, 0])
    if isinstance(load, list) and len(load) > 0:
        load_display = f"{load[0]:.2f}"
    else:
        load_display = "-.--"

    return f"{job_id} | {project} | {status} | {dispatch_display} UTC | {duration} | CPU: {load_display}"


def format_logs_header(job: Dict[str, Any], local_tz: str = DEFAULT_TIMEZONE) -> str:
    """Format the header for 'luzia logs' output with timing info.

    Returns a formatted header block with timing and system info.
    """
    job_id = job.get("id", "unknown")
    project = job.get("project", "unknown")
    status = job.get("status", "unknown")

    time_metrics = job.get("time_metrics", {})
    dispatch = time_metrics.get("dispatch", {})
    completion = time_metrics.get("completion", {})

    lines = [
        "═" * 65,
        f"Job: agent:{project}:{job_id}",
        f"Agent: {project}",
    ]

    # Dispatch time with local
    dispatch_time = dispatch.get("utc_time", job.get("started", ""))
    if dispatch_time:
        local_time = convert_to_local_time(dispatch_time, local_tz)
        lines.append(f"Dispatched: {dispatch_time} ({local_tz}: {local_time})")

    # Status with duration
    if completion.get("utc_time"):
        duration = completion.get("duration_formatted", "--:--:--")
        lines.append(f"Status: completed (took {duration})")
    elif dispatch_time:
        elapsed = elapsed_since(dispatch_time)
        lines.append(f"Status: {status} (running for {elapsed})")
    else:
        lines.append(f"Status: {status}")

    # System context
    ctx = dispatch if dispatch else completion
    if ctx:
        load = ctx.get("system_load", [0, 0, 0])
        mem = ctx.get("memory_percent", 0)
        disk = ctx.get("disk_percent", 0)
        if isinstance(load, list) and len(load) > 0:
            lines.append(f"System: CPU {load[0]:.2f}, Memory {mem}%, Disk {disk}%")

    lines.append("═" * 65)

    return "\n".join(lines)


# =============================================================================
# CONVENIENCE FUNCTION FOR LUZIA INTEGRATION
# =============================================================================

def create_task_time_metadata(task_id: str, project: str) -> Dict[str, Any]:
    """Create initial time metadata for a new task dispatch.

    Call this from spawn_claude_agent() to add time tracking.
    """
    tracker = TaskTimeTracker(task_id, project)
    dispatch_data = tracker.mark_dispatched()

    return {
        "time_metrics": dispatch_data,
        "time_tracker_data": {
            "task_id": task_id,
            "project": project,
            "dispatch_time": tracker.dispatch_time,
            "agent_timezone": tracker.agent_timezone
        }
    }


def update_task_completion_metadata(meta: Dict[str, Any], exit_code: int = 0) -> Dict[str, Any]:
    """Update task metadata with completion time metrics.

    Call this when a job completes to finalize time tracking.
    """
    tracker_data = meta.get("time_tracker_data", {})

    tracker = TaskTimeTracker(
        tracker_data.get("task_id", "unknown"),
        tracker_data.get("project", "unknown")
    )
    tracker.dispatch_time = tracker_data.get("dispatch_time")
    tracker.agent_timezone = tracker_data.get("agent_timezone", DEFAULT_TIMEZONE)

    completion_data = tracker.mark_completed(exit_code)

    # Merge with existing time_metrics
    time_metrics = meta.get("time_metrics", {})
    time_metrics.update(completion_data)

    # Record for baseline
    duration = completion_data.get("completion", {}).get("duration_seconds")
    if duration:
        try:
            record_task_duration(
                tracker.project,
                duration,
                exit_code,
                system_load=tracker.completion_context.get("system_load", [0])[0],
                memory_percent=tracker.completion_context.get("memory", {}).get("used_percent")
            )
        except Exception:
            pass  # Don't fail on baseline recording errors

    return {"time_metrics": time_metrics}


if __name__ == "__main__":
    # Quick test
    print("Time Metrics Module Test")
    print("=" * 40)
    print(f"Current UTC: {get_utc_now()}")
    print(f"System Context: {json.dumps(capture_system_context(), indent=2)}")

    # Test tracker
    tracker = TaskTimeTracker("test-001", "admin")
    print(f"\nDispatch: {json.dumps(tracker.mark_dispatched(), indent=2)}")

    import time as t
    t.sleep(1)

    print(f"\nCompletion: {json.dumps(tracker.mark_completed(0), indent=2)}")
    print(f"\nFull Metrics: {json.dumps(tracker.get_full_metrics(), indent=2)}")
