# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""Data models for the Agent service."""

from typing import List, Dict, Any, Optional
from enum import Enum
from pydantic import BaseModel, Field
from modules.shared.timeUtils import getUtcTimestamp
import uuid


class AgentStatusEnum(str, Enum):
    RUNNING = "running"
    COMPLETED = "completed"
    MAX_ROUNDS_REACHED = "maxRoundsReached"
    BUDGET_EXCEEDED = "budgetExceeded"
    ERROR = "error"
    STOPPED = "stopped"


class AgentEventTypeEnum(str, Enum):
    MESSAGE = "message"
    CHUNK = "chunk"
    TOOL_CALL = "toolCall"
    TOOL_RESULT = "toolResult"
    AGENT_PROGRESS = "agentProgress"
    AGENT_SUMMARY = "agentSummary"
    FILE_CREATED = "fileCreated"
    FILE_UPDATED = "fileUpdated"
    FILE_EDIT_PROPOSAL = "fileEditProposal"
    FILE_VERSION = "fileVersion"
    FILE_EDIT_REJECTED = "fileEditRejected"
    DATA_SOURCE_ACCESS = "dataSourceAccess"
    VOICE_RESPONSE = "voiceResponse"
    FINAL = "final"
    ERROR = "error"


class ToolDefinition(BaseModel):
    """Schema for a tool available to the agent."""
    name: str = Field(description="Unique tool name")
    description: str = Field(description="What this tool does")
    parameters: Dict[str, Any] = Field(
        default_factory=dict,
        description="JSON Schema for tool parameters"
    )
    readOnly: bool = Field(
        default=False,
        description="If True, tool can run in parallel with other readOnly tools"
    )
    featureType: Optional[str] = Field(
        default=None,
        description="Feature scope for this tool (None = available to all)"
    )
    toolSet: Optional[str] = Field(
        default=None,
        description="Tool-set scope (None = available to all sets, e.g. 'core', 'workspace')"
    )


class ToolCallRequest(BaseModel):
    """A tool call requested by the AI model."""
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str
    args: Dict[str, Any] = Field(default_factory=dict)


class ToolResult(BaseModel):
    """Result from executing a tool."""
    toolCallId: str
    toolName: str
    success: bool = True
    data: str = ""
    error: Optional[str] = None
    durationMs: int = 0
    sideEvents: Optional[List[Dict[str, Any]]] = None


class AgentEvent(BaseModel):
    """Event emitted during agent execution for SSE streaming."""
    type: AgentEventTypeEnum
    content: Optional[str] = None
    data: Optional[Dict[str, Any]] = None


class AgentConfig(BaseModel):
    """Configuration for an agent run."""
    maxRounds: int = Field(default=25, ge=1, le=100)
    maxCostCHF: Optional[float] = Field(default=None, ge=0.0)
    toolSet: str = Field(default="core")
    temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)


class AgentState(BaseModel):
    """Tracks state across an agent loop execution."""
    workflowId: str
    currentRound: int = 0
    maxRounds: int = 25
    totalAiCalls: int = 0
    totalToolCalls: int = 0
    totalCostCHF: float = 0.0
    totalProcessingTime: float = 0.0
    status: AgentStatusEnum = AgentStatusEnum.RUNNING
    abortReason: Optional[str] = None


class ToolCallLog(BaseModel):
    """Log of a single tool call for observability."""
    toolName: str
    args: Dict[str, Any] = Field(default_factory=dict)
    success: bool = True
    durationMs: int = 0
    error: Optional[str] = None
    resultData: str = Field(default="", description="Short result summary for artifact tracking")


class AgentRoundLog(BaseModel):
    """Log of a single agent round for observability."""
    roundNumber: int
    aiModel: str = ""
    inputTokens: int = 0
    outputTokens: int = 0
    costCHF: float = 0.0
    toolCalls: List[ToolCallLog] = Field(default_factory=list)
    durationMs: int = 0


class AgentTrace(BaseModel):
    """Full trace of an agent workflow for observability."""
    workflowId: str
    userId: str = ""
    featureInstanceId: str = ""
    startedAt: float = Field(default_factory=getUtcTimestamp)
    completedAt: Optional[float] = None
    status: AgentStatusEnum = AgentStatusEnum.RUNNING
    totalRounds: int = 0
    totalToolCalls: int = 0
    totalCostCHF: float = 0.0
    abortReason: Optional[str] = None
    rounds: List[AgentRoundLog] = Field(default_factory=list)


class PendingFileEdit(BaseModel):
    """A proposed file edit awaiting user approval."""
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    fileId: str
    fileName: str
    mimeType: str = ""
    oldContent: str = ""
    newContent: str = ""
    status: str = Field(default="pending", description="pending | accepted | rejected")
    toolCallId: str = ""
    workflowId: str = ""
