"""
Outcome Capture — Phase 23.7

Records what happened after an Experience/WorkflowTemplate was applied.
This is the raw feedback data that feeds the Self-Improving Loop.

Sources:
  - tool execution results
  - agent output signals
  - explicit user/caller feedback (optional)

Outcome is deliberately lightweight — it does NOT trigger any optimization.
The EvaluationEngine and SafetyGuard decide when/whether to act on outcomes.
"""

import time
import uuid
from dataclasses import dataclass, field
from typing import Any, Dict, Optional


OUTCOME_RESULTS = {"success", "failure", "partial", "unknown"}


@dataclass
class Outcome:
    """
    Single execution outcome for an Experience or WorkflowTemplate.

    Fields:
        outcome_id      — unique ID (out_<hex>)
        experience_id   — which experience was applied
        workflow_id     — which workflow template was applied (optional)
        task_id         — task context
        agent_id        — agent that executed
        result          — success / failure / partial / unknown
        metrics         — arbitrary quantitative measures
                          e.g. { latency: 2.3, tokens_used: 400, error: False }
        agent_feedback  — free-text signal from agent ("deploy succeeded")
        score           — caller-provided score 0-1 (optional, -1 = not set)
        timestamp       — unix timestamp
        metadata        — extra fields
    """
    outcome_id: str = field(default_factory=lambda: f"out_{uuid.uuid4().hex[:12]}")
    experience_id: str = ""
    workflow_id: str = ""
    task_id: str = ""
    agent_id: str = ""
    result: str = "unknown"
    metrics: Dict[str, Any] = field(default_factory=dict)
    agent_feedback: str = ""
    score: float = -1.0
    timestamp: float = field(default_factory=time.time)
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if self.result not in OUTCOME_RESULTS:
            self.result = "unknown"
        if self.score != -1.0:
            self.score = max(0.0, min(1.0, float(self.score)))

    @property
    def is_success(self) -> bool:
        return self.result == "success"

    @property
    def is_failure(self) -> bool:
        return self.result == "failure"

    @property
    def explicit_score(self) -> Optional[float]:
        """Returns score if explicitly set, else None."""
        return self.score if self.score >= 0 else None

    @property
    def latency(self) -> Optional[float]:
        return self.metrics.get("latency")

    @property
    def has_error(self) -> bool:
        return bool(self.metrics.get("error", False))

    def to_dict(self) -> dict:
        import json
        return {
            "outcome_id": self.outcome_id,
            "experience_id": self.experience_id,
            "workflow_id": self.workflow_id,
            "task_id": self.task_id,
            "agent_id": self.agent_id,
            "result": self.result,
            "metrics": self.metrics,
            "agent_feedback": self.agent_feedback,
            "score": self.score,
            "timestamp": self.timestamp,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, d: dict) -> "Outcome":
        import json

        def _loads(v, default):
            if isinstance(v, str):
                try:
                    return json.loads(v)
                except Exception:
                    return default
            return v if v is not None else default

        return cls(
            outcome_id=d.get("outcome_id", f"out_{uuid.uuid4().hex[:12]}"),
            experience_id=d.get("experience_id", ""),
            workflow_id=d.get("workflow_id", ""),
            task_id=d.get("task_id", ""),
            agent_id=d.get("agent_id", ""),
            result=d.get("result", "unknown"),
            metrics=_loads(d.get("metrics", {}), {}),
            agent_feedback=d.get("agent_feedback", ""),
            score=float(d.get("score", -1.0)),
            timestamp=float(d.get("timestamp", time.time())),
            metadata=_loads(d.get("metadata", {}), {}),
        )
