"""Implicit handoff detection for CrewAI task context dependencies.

Detects handoffs that occur through CrewAI's task context mechanism,
where one task's output becomes context for another task. This captures
the agent-to-agent information flow even without explicit delegation tools.

This enables context drift detection for normal crew execution patterns
where tasks flow sequentially or through dependencies rather than
explicit delegate_work_to_coworker calls.
"""

from __future__ import annotations

import hashlib
import json
import uuid
from typing import TYPE_CHECKING, Any, Optional

if TYPE_CHECKING:
    from ..run import ArzuleRun


def _compute_content_hash(content: Any) -> Optional[str]:
    """Compute hash of content for drift detection."""
    if content is None:
        return None
    try:
        if isinstance(content, dict):
            serialized = json.dumps(content, sort_keys=True, default=str)
        else:
            serialized = str(content)
        return hashlib.md5(serialized.encode()).hexdigest()[:12]
    except Exception:
        return None


def _extract_context_tasks(task: Any) -> list[Any]:
    """
    Extract context tasks from a CrewAI task.
    
    CrewAI tasks can have `context` which is a list of other tasks
    whose outputs should be available to this task.
    """
    context = getattr(task, "context", None)
    if context is None:
        return []
    
    if isinstance(context, (list, tuple)):
        return list(context)
    
    # Single task as context
    return [context] if context else []


def _get_task_output(task: Any) -> Optional[str]:
    """Extract the output from a completed task."""
    # Try various attributes where CrewAI stores task output
    for attr in ("output", "result", "raw_output", "output_raw"):
        output = getattr(task, attr, None)
        if output is not None:
            if hasattr(output, "raw"):
                return str(output.raw)
            return str(output)
    return None


def _get_task_description(task: Any) -> Optional[str]:
    """Get task description/expected output for comparison."""
    desc = getattr(task, "description", None)
    expected = getattr(task, "expected_output", None)
    
    parts = []
    if desc:
        parts.append(str(desc))
    if expected:
        parts.append(f"Expected: {expected}")
    
    return " | ".join(parts) if parts else None


def _get_task_identifier(task: Any) -> str:
    """Get a unique identifier for a task."""
    task_id = getattr(task, "id", None)
    if task_id:
        return str(task_id)
    
    task_name = getattr(task, "name", None)
    if task_name:
        return task_name
    
    # Fall back to description hash
    desc = getattr(task, "description", "")
    return f"task:{hashlib.md5(str(desc)[:100].encode()).hexdigest()[:8]}"


def _get_agent_role(task: Any) -> Optional[str]:
    """Get the agent role assigned to a task."""
    agent = getattr(task, "agent", None)
    if agent:
        return getattr(agent, "role", None)
    return None


def detect_task_context_handoff(
    run: "ArzuleRun",
    task: Any,
    span_id: Optional[str] = None,
) -> list[str]:
    """
    Detect implicit handoffs from context tasks and emit handoff.proposed events.
    
    When a task has context from other tasks, each context task represents
    an implicit handoff of information. We emit handoff.proposed for each.
    
    Args:
        run: The active ArzuleRun
        task: The CrewAI task that is starting
        span_id: The current span ID
        
    Returns:
        List of handoff keys generated for tracking
    """
    context_tasks = _extract_context_tasks(task)
    if not context_tasks:
        return []
    
    handoff_keys = []
    receiving_agent = _get_agent_role(task)
    receiving_task_id = _get_task_identifier(task)
    
    for ctx_task in context_tasks:
        # Get info about the providing task
        providing_agent = _get_agent_role(ctx_task)
        providing_task_id = _get_task_identifier(ctx_task)
        ctx_output = _get_task_output(ctx_task)
        ctx_description = _get_task_description(ctx_task)
        
        # Generate handoff key
        handoff_key = str(uuid.uuid4())
        handoff_keys.append(handoff_key)
        
        # Store pending handoff for correlation
        run._handoff_pending[handoff_key] = {
            "type": "implicit_context",
            "from_role": providing_agent,
            "from_task_id": providing_task_id,
            "to_role": receiving_agent,
            "to_task_id": receiving_task_id,
            "proposed_at": run.now(),
            "context_output": ctx_output,
            "context_description": ctx_description,
        }
        
        # Build payload for semantic analysis
        payload = {
            "context_source": {
                "task_id": providing_task_id,
                "agent_role": providing_agent,
                "description": ctx_description,
            },
            "context_output": ctx_output,
        }
        
        # Compute hash for contract drift detection
        content_hash = _compute_content_hash(ctx_output)
        
        # Emit handoff.proposed event
        run.emit({
            "schema_version": "trace_event.v0_1",
            "run_id": run.run_id,
            "tenant_id": run.tenant_id,
            "project_id": run.project_id,
            "trace_id": run.trace_id,
            "span_id": span_id,
            "parent_span_id": run.current_parent_span_id(),
            "seq": run.next_seq(),
            "ts": run.now(),
            "agent": {
                "id": f"crewai:role:{providing_agent}" if providing_agent else None,
                "role": providing_agent,
            } if providing_agent else None,
            "event_type": "handoff.proposed",
            "status": "ok",
            "summary": f"context handoff: {providing_agent or 'task'} -> {receiving_agent or 'task'}",
            "attrs_compact": {
                "handoff_key": handoff_key,
                "handoff_type": "implicit_context",
                "from_agent_role": providing_agent,
                "from_task_id": providing_task_id,
                "to_agent_role": receiving_agent,
                "to_task_id": receiving_task_id,
                "payload_hash": content_hash,
            },
            "payload": payload,
            "raw_ref": {"storage": "inline"},
        })
    
    return handoff_keys


def emit_implicit_handoff_complete(
    run: "ArzuleRun",
    task: Any,
    status: str = "ok",
    span_id: Optional[str] = None,
) -> int:
    """
    Emit handoff.complete events for all implicit context handoffs to this task.
    
    Called when a task completes. Looks up any pending implicit handoffs
    that targeted this task and emits completion events with the task's result.
    
    Args:
        run: The active ArzuleRun
        task: The completed CrewAI task
        status: Completion status
        span_id: The current span ID
        
    Returns:
        Number of handoff.complete events emitted
    """
    task_id = _get_task_identifier(task)
    agent_role = _get_agent_role(task)
    task_output = _get_task_output(task)
    
    # Find all pending handoffs targeting this task
    completed_count = 0
    keys_to_remove = []
    
    for handoff_key, pending in list(run._handoff_pending.items()):
        # Only process implicit context handoffs for this task
        if pending.get("type") != "implicit_context":
            continue
        if pending.get("to_task_id") != task_id:
            continue
        
        keys_to_remove.append(handoff_key)
        
        # Get the original context that was provided
        context_output = pending.get("context_output")
        from_agent = pending.get("from_role")
        from_task = pending.get("from_task_id")
        
        # Build payload with both what was received and what was produced
        payload = {
            "received_context": {
                "from_agent": from_agent,
                "from_task": from_task,
                "content": context_output[:500] if context_output else None,
            },
            "task_result": task_output[:500] if task_output else None,
        }
        
        # Compute result hash
        result_hash = _compute_content_hash(task_output)
        
        # Create result summary
        result_summary = None
        if task_output:
            result_summary = task_output[:100] + "..." if len(task_output) > 100 else task_output
        
        # Emit handoff.complete
        run.emit({
            "schema_version": "trace_event.v0_1",
            "run_id": run.run_id,
            "tenant_id": run.tenant_id,
            "project_id": run.project_id,
            "trace_id": run.trace_id,
            "span_id": span_id,
            "parent_span_id": run.current_parent_span_id(),
            "seq": run.next_seq(),
            "ts": run.now(),
            "agent": {
                "id": f"crewai:role:{agent_role}" if agent_role else None,
                "role": agent_role,
            } if agent_role else None,
            "event_type": "handoff.complete",
            "status": status,
            "summary": result_summary or f"context processed by {agent_role or 'task'}",
            "attrs_compact": {
                "handoff_key": handoff_key,
                "handoff_type": "implicit_context",
                "from_agent_role": from_agent,
                "to_agent_role": agent_role,
                "result": result_summary,
                "result_hash": result_hash,
            },
            "payload": payload,
            "raw_ref": {"storage": "inline"},
        })
        
        completed_count += 1
    
    # Clean up processed handoffs
    for key in keys_to_remove:
        run._handoff_pending.pop(key, None)
    
    return completed_count

