"""
Persistence Models - Dataclasses for database entities.

These models represent the core entities stored in the SQLite database.
They provide type safety and easy serialization for database operations.
"""

from dataclasses import dataclass, field, asdict
from datetime import datetime, date
from typing import Any, Optional, Union
from enum import Enum


def parse_datetime(val: Any) -> Optional[datetime]:
    """Parse a datetime from DB row (string or datetime)."""
    if val is None:
        return None
    if isinstance(val, datetime):
        return val
    if isinstance(val, str):
        try:
            return datetime.fromisoformat(val)
        except ValueError:
            # Fallback for some SQLite date string formats if needed
            # For now assume ISO format as that's what we store
            return None
    return None


def parse_date(val: Any) -> Optional[date]:
    """Parse a date from DB row (string or date)."""
    if val is None:
        return None
    if isinstance(val, date):
        return val
    if isinstance(val, str):
        try:
            return date.fromisoformat(val)
        except ValueError:
            return None
    return None


class AgentTool(Enum):
    """Types of agent tools."""

    CLAUDE_CODE = "claude_code"
    GEMINI_CLI = "gemini_cli"
    CODEX = "codex"
    CLAUDE_SDK = "claude_sdk"
    OPENAI_AGENTS = "openai_agents"
    GEMINI_API = "gemini_api"


class AgentStatusEnum(Enum):
    """Agent status values."""

    IDLE = "idle"
    RUNNING = "running"
    STUCK = "stuck"
    PAUSED = "paused"
    TERMINATED = "terminated"


class TaskStatus(Enum):
    """Task status values."""

    PENDING = "pending"
    ASSIGNED = "assigned"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    BLOCKED = "blocked"


class RunOutcome(Enum):
    """Run outcome values."""

    SUCCESS = "success"
    FAILURE = "failure"
    BLOCKED = "blocked"
    TIMEOUT = "timeout"
    TERMINATED = "terminated"


class ApprovalStatus(Enum):
    """Approval status values."""

    PENDING = "pending"
    APPROVED = "approved"
    REJECTED = "rejected"
    TIMEOUT = "timeout"
    SKIPPED = "skipped"


class RiskLevelEnum(Enum):
    """Risk level values."""

    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


@dataclass
class Agent:
    """Database model for an agent."""

    id: str
    tool: str  # AgentTool value
    worktree_path: Optional[str] = None
    branch: Optional[str] = None
    status: str = "idle"  # AgentStatusEnum value
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "id": self.id,
            "tool": self.tool,
            "worktree_path": self.worktree_path,
            "branch": self.branch,
            "status": self.status,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "Agent":
        """Create from database row."""
        return cls(
            id=row["id"],
            tool=row["tool"],
            worktree_path=row.get("worktree_path"),
            branch=row.get("branch"),
            status=row.get("status", "idle"),
            created_at=parse_datetime(row.get("created_at")),
            updated_at=parse_datetime(row.get("updated_at")),
        )


@dataclass
class Task:
    """Database model for a task."""

    id: str
    description: str
    task_type: str
    priority: int = 0
    status: str = "pending"  # TaskStatus value
    assigned_agent_id: Optional[str] = None
    created_at: Optional[datetime] = None
    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    error_message: Optional[str] = None
    # Run-until-done support
    run_until_done: bool = False
    max_retries: int = 3
    attempt_count: int = 0
    last_attempt_at: Optional[datetime] = None
    success_criteria: Optional[str] = None  # JSON string

    @property
    def assigned_to(self) -> Optional[str]:
        """Alias for assigned_agent_id for code compatibility."""
        return self.assigned_agent_id

    @assigned_to.setter
    def assigned_to(self, value: Optional[str]) -> None:
        """Set assigned_agent_id via alias."""
        self.assigned_agent_id = value

    @property
    def can_retry(self) -> bool:
        """Check if task can be retried."""
        return (
            self.run_until_done
            and self.attempt_count < self.max_retries
            and self.status == "failed"
        )

    @property
    def retries_remaining(self) -> int:
        """Get number of retries remaining."""
        return max(0, self.max_retries - self.attempt_count)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "id": self.id,
            "description": self.description,
            "task_type": self.task_type,
            "priority": self.priority,
            "status": self.status,
            "assigned_agent_id": self.assigned_agent_id,
            "error_message": self.error_message,
            "run_until_done": self.run_until_done,
            "max_retries": self.max_retries,
            "attempt_count": self.attempt_count,
            "success_criteria": self.success_criteria,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "Task":
        """Create from database row."""
        return cls(
            id=row["id"],
            description=row["description"],
            task_type=row["task_type"],
            priority=row.get("priority", 0),
            status=row.get("status", "pending"),
            assigned_agent_id=row.get("assigned_agent_id"),
            created_at=parse_datetime(row.get("created_at")),
            started_at=parse_datetime(row.get("started_at")),
            completed_at=parse_datetime(row.get("completed_at")),
            error_message=row.get("error_message"),
            run_until_done=bool(row.get("run_until_done", False)),
            max_retries=row.get("max_retries", 3),
            attempt_count=row.get("attempt_count", 0),
            last_attempt_at=parse_datetime(row.get("last_attempt_at")),
            success_criteria=row.get("success_criteria"),
        )


@dataclass
class Run:
    """Database model for an execution run."""

    id: str
    agent_id: str
    task_id: Optional[str] = None
    started_at: Optional[datetime] = None
    ended_at: Optional[datetime] = None
    outcome: Optional[str] = None  # RunOutcome value
    tokens_input: int = 0
    tokens_output: int = 0
    cost_usd: float = 0.0
    error_message: Optional[str] = None
    status_packet_json: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "id": self.id,
            "agent_id": self.agent_id,
            "task_id": self.task_id,
            "outcome": self.outcome,
            "tokens_input": self.tokens_input,
            "tokens_output": self.tokens_output,
            "cost_usd": self.cost_usd,
            "error_message": self.error_message,
            "status_packet_json": self.status_packet_json,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "Run":
        """Create from database row."""
        return cls(
            id=row["id"],
            agent_id=row["agent_id"],
            task_id=row.get("task_id"),
            started_at=parse_datetime(row.get("started_at")),
            ended_at=parse_datetime(row.get("ended_at")),
            outcome=row.get("outcome"),
            tokens_input=row.get("tokens_input", 0),
            tokens_output=row.get("tokens_output", 0),
            cost_usd=row.get("cost_usd", 0.0),
            error_message=row.get("error_message"),
            status_packet_json=row.get("status_packet_json"),
        )

    def total_tokens(self) -> int:
        """Get total tokens used."""
        return self.tokens_input + self.tokens_output


@dataclass
class HealthSample:
    """Database model for a health check sample."""

    id: Optional[int] = None
    agent_id: str = ""
    sampled_at: Optional[datetime] = None
    last_stdout_at: Optional[datetime] = None
    last_file_change_at: Optional[datetime] = None
    token_burn_rate: Optional[float] = None
    error_count: int = 0
    consecutive_same_error: int = 0
    pending_permission_prompt: bool = False
    edit_revert_cycles: int = 0
    is_stuck: bool = False
    stuck_reason: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "agent_id": self.agent_id,
            "last_stdout_at": self.last_stdout_at,
            "last_file_change_at": self.last_file_change_at,
            "token_burn_rate": self.token_burn_rate,
            "error_count": self.error_count,
            "consecutive_same_error": self.consecutive_same_error,
            "pending_permission_prompt": self.pending_permission_prompt,
            "edit_revert_cycles": self.edit_revert_cycles,
            "is_stuck": self.is_stuck,
            "stuck_reason": self.stuck_reason,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "HealthSample":
        """Create from database row."""
        return cls(
            id=row.get("id"),
            agent_id=row["agent_id"],
            sampled_at=parse_datetime(row.get("sampled_at")),
            last_stdout_at=parse_datetime(row.get("last_stdout_at")),
            last_file_change_at=parse_datetime(row.get("last_file_change_at")),
            token_burn_rate=row.get("token_burn_rate"),
            error_count=row.get("error_count", 0),
            consecutive_same_error=row.get("consecutive_same_error", 0),
            pending_permission_prompt=bool(row.get("pending_permission_prompt", False)),
            edit_revert_cycles=row.get("edit_revert_cycles", 0),
            is_stuck=bool(row.get("is_stuck", False)),
            stuck_reason=row.get("stuck_reason"),
        )


@dataclass
class Approval:
    """Database model for an approval request."""

    id: str
    agent_id: str
    action_type: str
    target: str
    risk_level: str  # RiskLevelEnum value
    run_id: Optional[str] = None
    status: str = "pending"  # ApprovalStatus value
    requested_at: Optional[datetime] = None
    decided_at: Optional[datetime] = None
    decided_by: Optional[str] = None
    decision_notes: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "id": self.id,
            "agent_id": self.agent_id,
            "run_id": self.run_id,
            "action_type": self.action_type,
            "target": self.target,
            "risk_level": self.risk_level,
            "status": self.status,
            "decided_by": self.decided_by,
            "decision_notes": self.decision_notes,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "Approval":
        """Create from database row."""
        return cls(
            id=row["id"],
            agent_id=row["agent_id"],
            run_id=row.get("run_id"),
            action_type=row["action_type"],
            target=row["target"],
            risk_level=row["risk_level"],
            status=row.get("status", "pending"),
            requested_at=parse_datetime(row.get("requested_at")),
            decided_at=parse_datetime(row.get("decided_at")),
            decided_by=row.get("decided_by"),
            decision_notes=row.get("decision_notes"),
        )


@dataclass
class UsageDaily:
    """Database model for daily usage aggregates."""

    id: Optional[int] = None
    date: Optional[date] = None
    agent_id: str = ""
    tokens_input: int = 0
    tokens_output: int = 0
    cost_usd: float = 0.0
    requests_count: int = 0
    errors_count: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "date": self.date,
            "agent_id": self.agent_id,
            "tokens_input": self.tokens_input,
            "tokens_output": self.tokens_output,
            "cost_usd": self.cost_usd,
            "requests_count": self.requests_count,
            "errors_count": self.errors_count,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "UsageDaily":
        """Create from database row."""
        return cls(
            id=row.get("id"),
            date=parse_date(row.get("date")),
            agent_id=row["agent_id"],
            tokens_input=row.get("tokens_input", 0),
            tokens_output=row.get("tokens_output", 0),
            cost_usd=row.get("cost_usd", 0.0),
            requests_count=row.get("requests_count", 0),
            errors_count=row.get("errors_count", 0),
        )

    def total_tokens(self) -> int:
        """Get total tokens used."""
        return self.tokens_input + self.tokens_output


@dataclass
class MCPToolUsage:
    """Database model for MCP tool usage tracking."""

    id: Optional[int] = None
    agent_id: str = ""
    run_id: Optional[str] = None
    tool_name: str = ""
    mcp_server: str = ""
    called_at: Optional[datetime] = None
    tokens_used: int = 0
    duration_ms: Optional[int] = None
    success: bool = True
    error_message: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "agent_id": self.agent_id,
            "run_id": self.run_id,
            "tool_name": self.tool_name,
            "mcp_server": self.mcp_server,
            "tokens_used": self.tokens_used,
            "duration_ms": self.duration_ms,
            "success": self.success,
            "error_message": self.error_message,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "MCPToolUsage":
        """Create from database row."""
        return cls(
            id=row.get("id"),
            agent_id=row["agent_id"],
            run_id=row.get("run_id"),
            tool_name=row["tool_name"],
            mcp_server=row["mcp_server"],
            called_at=parse_datetime(row.get("called_at")),
            tokens_used=row.get("tokens_used", 0),
            duration_ms=row.get("duration_ms"),
            success=bool(row.get("success", True)),
            error_message=row.get("error_message"),
        )


@dataclass
class Decision:
    """Database model for significant decisions made by agents."""

    id: str
    agent_id: str
    decision: str
    task_id: Optional[str] = None
    rationale: Optional[str] = None
    reversible: bool = True
    created_at: Optional[datetime] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "id": self.id,
            "agent_id": self.agent_id,
            "task_id": self.task_id,
            "decision": self.decision,
            "rationale": self.rationale,
            "reversible": self.reversible,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "Decision":
        """Create from database row."""
        return cls(
            id=row["id"],
            agent_id=row["agent_id"],
            task_id=row.get("task_id"),
            decision=row["decision"],
            rationale=row.get("rationale"),
            reversible=bool(row.get("reversible", True)),
            created_at=parse_datetime(row.get("created_at")),
        )


class QuorumType(Enum):
    """Voting quorum types."""

    SIMPLE_MAJORITY = "simple_majority"  # >50% of votes
    SUPERMAJORITY = "supermajority"  # Configurable threshold (default 67%)
    UNANIMOUS = "unanimous"  # 100% agreement


class VotingStatus(Enum):
    """Voting session status values."""

    OPEN = "open"
    CLOSED = "closed"
    CANCELLED = "cancelled"


@dataclass
class VotingSession:
    """Database model for a voting session."""

    id: str
    topic: str
    created_by: str
    task_id: Optional[str] = None
    description: Optional[str] = None
    created_at: Optional[datetime] = None
    deadline_at: Optional[datetime] = None
    status: str = "open"  # VotingStatus value
    closed_at: Optional[datetime] = None
    required_voters: int = 3
    quorum_type: str = "simple_majority"  # QuorumType value
    supermajority_threshold: float = 0.67
    winning_option: Optional[str] = None
    result_rationale: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "id": self.id,
            "task_id": self.task_id,
            "topic": self.topic,
            "description": self.description,
            "created_by": self.created_by,
            "deadline_at": self.deadline_at,
            "status": self.status,
            "required_voters": self.required_voters,
            "quorum_type": self.quorum_type,
            "supermajority_threshold": self.supermajority_threshold,
            "winning_option": self.winning_option,
            "result_rationale": self.result_rationale,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "VotingSession":
        """Create from database row."""
        return cls(
            id=row["id"],
            task_id=row.get("task_id"),
            topic=row["topic"],
            description=row.get("description"),
            created_by=row["created_by"],
            created_at=parse_datetime(row.get("created_at")),
            deadline_at=parse_datetime(row.get("deadline_at")),
            status=row.get("status", "open"),
            closed_at=parse_datetime(row.get("closed_at")),
            required_voters=row.get("required_voters", 3),
            quorum_type=row.get("quorum_type", "simple_majority"),
            supermajority_threshold=row.get("supermajority_threshold", 0.67),
            winning_option=row.get("winning_option"),
            result_rationale=row.get("result_rationale"),
        )

    @property
    def is_open(self) -> bool:
        """Check if voting is still open."""
        return self.status == "open"


@dataclass
class Vote:
    """Database model for an individual vote."""

    id: Optional[int] = None
    session_id: str = ""
    agent_id: str = ""
    choice: str = ""
    confidence: float = 1.0
    rationale: Optional[str] = None
    voted_at: Optional[datetime] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for database insertion."""
        return {
            "session_id": self.session_id,
            "agent_id": self.agent_id,
            "choice": self.choice,
            "confidence": self.confidence,
            "rationale": self.rationale,
        }

    @classmethod
    def from_row(cls, row: dict[str, Any]) -> "Vote":
        """Create from database row."""
        return cls(
            id=row.get("id"),
            session_id=row["session_id"],
            agent_id=row["agent_id"],
            choice=row["choice"],
            confidence=row.get("confidence", 1.0),
            rationale=row.get("rationale"),
            voted_at=parse_datetime(row.get("voted_at")),
        )