"""
Oracle State Machine - MADACE integration for Oracle Agent

Extends Oracle Agent with MADACE state machine for structured analysis workflows.
"""

import json
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Optional


class QueryState(Enum):
    """Oracle query states following MADACE methodology"""

    PENDING = "PENDING"  # Query created, not started
    ANALYZING = "ANALYZING"  # Oracle is analyzing
    REVIEWED = "REVIEWED"  # Analysis reviewed by user
    COMPLETED = "COMPLETED"  # Query completed and accepted
    FAILED = "FAILED"  # Query failed


class QueryPriority(Enum):
    """Query priority levels"""

    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4


@dataclass
class OracleQuery:
    """
    Represents a query in the Oracle state machine

    Follows MADACE principles:
    - State machine: PENDING → ANALYZING → REVIEWED → COMPLETED
    - Acceptance criteria: Must meet quality standards
    - Tracking: Full history of state transitions
    """

    id: str
    question: str
    state: QueryState = QueryState.PENDING
    priority: QueryPriority = QueryPriority.MEDIUM
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    started_at: Optional[str] = None
    completed_at: Optional[str] = None
    answer: Optional[str] = None
    sources: list[str] = field(default_factory=list)
    confidence: Optional[str] = None
    execution_time: Optional[float] = None
    error: Optional[str] = None
    metadata: dict[str, Any] = field(default_factory=dict)
    transitions: list[dict[str, str]] = field(default_factory=list)

    def to_dict(self) -> dict:
        """Convert to dictionary"""
        return {
            "id": self.id,
            "question": self.question,
            "state": self.state.value,
            "priority": self.priority.value,
            "created_at": self.created_at,
            "started_at": self.started_at,
            "completed_at": self.completed_at,
            "answer": self.answer,
            "sources": self.sources,
            "confidence": self.confidence,
            "execution_time": self.execution_time,
            "error": self.error,
            "metadata": self.metadata,
            "transitions": self.transitions,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "OracleQuery":
        """Create from dictionary"""
        return cls(
            id=data["id"],
            question=data["question"],
            state=QueryState(data["state"]),
            priority=QueryPriority(data["priority"]),
            created_at=data["created_at"],
            started_at=data.get("started_at"),
            completed_at=data.get("completed_at"),
            answer=data.get("answer"),
            sources=data.get("sources", []),
            confidence=data.get("confidence"),
            execution_time=data.get("execution_time"),
            error=data.get("error"),
            metadata=data.get("metadata", {}),
            transitions=data.get("transitions", []),
        )

    def transition_to(self, new_state: QueryState, note: Optional[str] = None) -> None:
        """
        Transition query to new state

        Args:
            new_state: Target state
            note: Optional note about transition
        """
        old_state = self.state
        self.state = new_state

        # Record transition
        transition = {
            "from": old_state.value,
            "to": new_state.value,
            "timestamp": datetime.now().isoformat(),
        }
        if note:
            transition["note"] = note

        self.transitions.append(transition)

        # Update timestamps
        if new_state == QueryState.ANALYZING and not self.started_at:
            self.started_at = datetime.now().isoformat()
        elif new_state in [QueryState.COMPLETED, QueryState.FAILED] and not self.completed_at:
            self.completed_at = datetime.now().isoformat()


class OracleStateMachine:
    """
    State machine for Oracle Agent following MADACE principles

    Features:
    - Queue management: Multiple queries in PENDING
    - State enforcement: One query in ANALYZING at a time
    - History tracking: All state transitions logged
    - Priority handling: Higher priority queries first
    - Persistence: Saves state to .madace/oracle/

    State Machine:
        PENDING → ANALYZING → REVIEWED → COMPLETED
                       ↓
                    FAILED

    Rules:
    - Maximum 1 query in ANALYZING
    - Queries must transition sequentially
    - Failed queries can be restarted
    """

    def __init__(self, project_path: Path):
        """
        Initialize Oracle State Machine

        Args:
            project_path: Path to project root
        """
        self.project_path = Path(project_path).resolve()
        self.oracle_dir = self.project_path / ".madace" / "oracle"
        self.state_file = self.oracle_dir / "oracle_state.json"

        # Ensure directory exists
        self.oracle_dir.mkdir(parents=True, exist_ok=True)

        # Load or initialize state
        self.queries: dict[str, OracleQuery] = {}
        self.current_analyzing: Optional[str] = None
        self.query_counter = 0

        self._load_state()

    def _load_state(self) -> None:
        """Load state from disk"""
        if self.state_file.exists():
            try:
                data = json.loads(self.state_file.read_text())
                self.query_counter = data.get("query_counter", 0)
                self.current_analyzing = data.get("current_analyzing")

                # Load queries
                for query_data in data.get("queries", []):
                    query = OracleQuery.from_dict(query_data)
                    self.queries[query.id] = query

            except Exception as e:
                print(f"Warning: Could not load Oracle state: {e}")
                self._initialize_state()
        else:
            self._initialize_state()

    def _initialize_state(self) -> None:
        """Initialize empty state"""
        self.queries = {}
        self.current_analyzing = None
        self.query_counter = 0
        self._save_state()

    def _save_state(self) -> None:
        """Save state to disk"""
        state = {
            "query_counter": self.query_counter,
            "current_analyzing": self.current_analyzing,
            "queries": [q.to_dict() for q in self.queries.values()],
            "updated_at": datetime.now().isoformat(),
        }

        self.state_file.write_text(json.dumps(state, indent=2))

    def create_query(
        self, question: str, priority: QueryPriority = QueryPriority.MEDIUM, metadata: Optional[dict] = None
    ) -> OracleQuery:
        """
        Create a new query in PENDING state

        Args:
            question: Question to ask Oracle
            priority: Query priority
            metadata: Additional metadata

        Returns:
            Created OracleQuery
        """
        self.query_counter += 1
        query_id = f"OQ{self.query_counter:04d}"

        query = OracleQuery(
            id=query_id,
            question=question,
            priority=priority,
            metadata=metadata or {},
        )

        self.queries[query_id] = query
        self._save_state()

        return query

    def start_analysis(self, query_id: str) -> bool:
        """
        Start analyzing a query

        Args:
            query_id: Query ID to analyze

        Returns:
            True if started, False if blocked

        Raises:
            ValueError: If query not found
        """
        if query_id not in self.queries:
            raise ValueError(f"Query {query_id} not found")

        # Enforce: Only 1 query in ANALYZING
        if self.current_analyzing and self.current_analyzing != query_id:
            return False

        query = self.queries[query_id]

        # Ensure query is in PENDING or FAILED state
        if query.state not in [QueryState.PENDING, QueryState.FAILED]:
            return False

        # Transition to ANALYZING
        query.transition_to(QueryState.ANALYZING, "Started analysis")
        self.current_analyzing = query_id
        self._save_state()

        return True

    def complete_analysis(
        self,
        query_id: str,
        answer: str,
        sources: list[str],
        confidence: str,
        execution_time: float,
    ) -> None:
        """
        Mark analysis as complete and move to REVIEWED

        Args:
            query_id: Query ID
            answer: Oracle's answer
            sources: Source files referenced
            confidence: Confidence level
            execution_time: Execution time in seconds
        """
        if query_id not in self.queries:
            raise ValueError(f"Query {query_id} not found")

        query = self.queries[query_id]

        if query.state != QueryState.ANALYZING:
            raise ValueError(f"Query {query_id} is not in ANALYZING state")

        # Update query with results
        query.answer = answer
        query.sources = sources
        query.confidence = confidence
        query.execution_time = execution_time

        # Transition to REVIEWED
        query.transition_to(QueryState.REVIEWED, "Analysis complete, awaiting review")

        # Clear current analyzing
        if self.current_analyzing == query_id:
            self.current_analyzing = None

        self._save_state()

    def fail_analysis(self, query_id: str, error: str) -> None:
        """
        Mark analysis as failed

        Args:
            query_id: Query ID
            error: Error message
        """
        if query_id not in self.queries:
            raise ValueError(f"Query {query_id} not found")

        query = self.queries[query_id]
        query.error = error
        query.transition_to(QueryState.FAILED, f"Analysis failed: {error}")

        # Clear current analyzing
        if self.current_analyzing == query_id:
            self.current_analyzing = None

        self._save_state()

    def accept_query(self, query_id: str) -> None:
        """
        Accept reviewed query and mark as COMPLETED

        Args:
            query_id: Query ID
        """
        if query_id not in self.queries:
            raise ValueError(f"Query {query_id} not found")

        query = self.queries[query_id]

        if query.state != QueryState.REVIEWED:
            raise ValueError(f"Query {query_id} must be in REVIEWED state to accept")

        query.transition_to(QueryState.COMPLETED, "Accepted by user")
        self._save_state()

    def retry_query(self, query_id: str) -> bool:
        """
        Retry a failed query

        Args:
            query_id: Query ID

        Returns:
            True if retry started, False if blocked
        """
        if query_id not in self.queries:
            raise ValueError(f"Query {query_id} not found")

        query = self.queries[query_id]

        if query.state != QueryState.FAILED:
            raise ValueError(f"Query {query_id} is not in FAILED state")

        # Clear error
        query.error = None

        # Move back to PENDING
        query.transition_to(QueryState.PENDING, "Retry requested")
        self._save_state()

        # Try to start analysis
        return self.start_analysis(query_id)

    def get_next_pending(self) -> Optional[OracleQuery]:
        """
        Get next pending query by priority

        Returns:
            Next pending query or None
        """
        pending = [q for q in self.queries.values() if q.state == QueryState.PENDING]

        if not pending:
            return None

        # Sort by priority (descending) then by created_at (ascending)
        pending.sort(key=lambda q: (-q.priority.value, q.created_at))

        return pending[0]

    def get_status(self) -> dict[str, Any]:
        """
        Get current state machine status

        Returns:
            Status dictionary
        """
        queries_by_state = {state: [] for state in QueryState}

        for query in self.queries.values():
            queries_by_state[query.state].append(query)

        return {
            "total_queries": len(self.queries),
            "current_analyzing": self.current_analyzing,
            "pending": len(queries_by_state[QueryState.PENDING]),
            "analyzing": len(queries_by_state[QueryState.ANALYZING]),
            "reviewed": len(queries_by_state[QueryState.REVIEWED]),
            "completed": len(queries_by_state[QueryState.COMPLETED]),
            "failed": len(queries_by_state[QueryState.FAILED]),
            "queries": {state.value: [q.id for q in queries] for state, queries in queries_by_state.items()},
        }

    def get_query(self, query_id: str) -> Optional[OracleQuery]:
        """
        Get query by ID

        Args:
            query_id: Query ID

        Returns:
            OracleQuery or None
        """
        return self.queries.get(query_id)

    def list_queries(
        self,
        state: Optional[QueryState] = None,
        limit: Optional[int] = None,
    ) -> list[OracleQuery]:
        """
        List queries, optionally filtered by state

        Args:
            state: Filter by state (optional)
            limit: Maximum number of queries to return

        Returns:
            List of OracleQuery objects
        """
        queries = list(self.queries.values())

        if state:
            queries = [q for q in queries if q.state == state]

        # Sort by created_at descending (newest first)
        queries.sort(key=lambda q: q.created_at, reverse=True)

        if limit:
            queries = queries[:limit]

        return queries
