"""
CLI State Reader - Read native CLI agent state files.

Provides readers for:
- Claude Code (~/.claude/)
- Codex CLI (~/.codex/)
- Gemini CLI (~/.gemini/)

These readers access native state files to get accurate rate limits
and session states without relying on orchestrator-level tracking.
"""

import json
import logging
import os
import yaml
from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Optional

from .state_models import (
    RateLimitState,
    SessionState,
    CLIStateSnapshot,
    WindowType,
    WaitingState,
)

logger = logging.getLogger(__name__)


class CLIStateReader(ABC):
    """
    Base class for reading CLI agent state files.

    Subclasses implement reading for specific CLI tools.
    """

    CLI_TYPE: str = "unknown"

    def __init__(self, state_dir: Optional[Path] = None):
        """
        Initialize the state reader.

        Args:
            state_dir: Override default state directory
        """
        self.state_dir = Path(state_dir).expanduser() if state_dir else self._get_default_dir()
        self._last_error: Optional[str] = None

    @abstractmethod
    def _get_default_dir(self) -> Path:
        """Get the default state directory for this CLI."""
        pass

    @abstractmethod
    def get_rate_limit_state(self) -> Optional[RateLimitState]:
        """Get current rate limit state."""
        pass

    @abstractmethod
    def get_session_state(self) -> Optional[SessionState]:
        """Get current session state."""
        pass

    def is_waiting_for_input(self) -> bool:
        """Check if CLI is waiting for user input."""
        session = self.get_session_state()
        return session.waiting_for_input if session else False

    def exists(self) -> bool:
        """Check if state directory exists."""
        return self.state_dir.exists()

    def is_installed(self) -> bool:
        """Check if CLI is installed (has executable)."""
        # Default implementation - can be overridden
        return self.exists()

    def get_snapshot(self, agent_id: Optional[str] = None) -> CLIStateSnapshot:
        """
        Get a complete state snapshot.

        Args:
            agent_id: Optional agent ID (defaults to CLI type)

        Returns:
            CLIStateSnapshot with current state
        """
        agent_id = agent_id or self.CLI_TYPE
        self._last_error = None

        try:
            rate_limit = self.get_rate_limit_state()
            session = self.get_session_state()

            return CLIStateSnapshot(
                agent_type=self.CLI_TYPE,
                agent_id=agent_id,
                rate_limit=rate_limit,
                session=session,
                is_installed=self.is_installed(),
                state_dir_exists=self.exists(),
            )
        except Exception as e:
            self._last_error = str(e)
            logger.warning(f"Error getting state snapshot for {self.CLI_TYPE}: {e}")
            return CLIStateSnapshot(
                agent_type=self.CLI_TYPE,
                agent_id=agent_id,
                is_installed=self.is_installed(),
                state_dir_exists=self.exists(),
                last_error=str(e),
            )

    def _read_json_file(self, path: Path) -> Optional[dict[str, Any]]:
        """Safely read a JSON file."""
        try:
            if not path.exists():
                return None
            with open(path) as f:
                return json.load(f)
        except (json.JSONDecodeError, IOError) as e:
            logger.debug(f"Error reading {path}: {e}")
            return None

    def _read_yaml_file(self, path: Path) -> Optional[dict[str, Any]]:
        """Safely read a YAML file."""
        try:
            if not path.exists():
                return None
            with open(path) as f:
                return yaml.safe_load(f)
        except (yaml.YAMLError, IOError) as e:
            logger.debug(f"Error reading {path}: {e}")
            return None

    def _get_most_recent_file(self, directory: Path, pattern: str) -> Optional[Path]:
        """Get the most recently modified file matching pattern."""
        if not directory.exists():
            return None
        files = list(directory.glob(pattern))
        if not files:
            return None
        return max(files, key=lambda f: f.stat().st_mtime)


class ClaudeStateReader(CLIStateReader):
    """
    Read Claude Code state files from ~/.claude/

    Claude Code stores:
    - Session data in ~/.claude/projects/{project}/sessions/
    - Usage data embedded in session files
    - Config in ~/.claude/config.json
    """

    CLI_TYPE = "claude"

    def _get_default_dir(self) -> Path:
        return Path("~/.claude").expanduser()

    def is_installed(self) -> bool:
        """Check if Claude Code CLI is installed."""
        import shutil
        return shutil.which("claude") is not None or self.exists()

    def get_rate_limit_state(self) -> Optional[RateLimitState]:
        """Parse Claude Code rate limits from session files."""
        # Look for sessions in projects directory
        projects_dir = self.state_dir / "projects"
        if not projects_dir.exists():
            return None

        # Find most recent session file across all projects
        latest_session = None
        latest_mtime = 0

        for project_dir in projects_dir.iterdir():
            if not project_dir.is_dir():
                continue
            session_file = self._get_most_recent_file(project_dir, "*.jsonl")
            if session_file and session_file.stat().st_mtime > latest_mtime:
                latest_session = session_file
                latest_mtime = session_file.stat().st_mtime

        if not latest_session:
            return None

        try:
            # Read the JSONL file and parse usage info
            # Claude Code stores conversation turns, each line is a message
            with open(latest_session) as f:
                lines = f.readlines()

            messages_sent = len([l for l in lines if '"role":"user"' in l or '"role":"assistant"' in l])

            # Default limits based on typical Claude Max subscription
            # These would ideally be read from config or API
            default_limit = 225  # Claude Max 5h limit

            # Estimate reset time (5 hour window from first message)
            if messages_sent > 0:
                first_msg_time = datetime.fromtimestamp(latest_session.stat().st_ctime)
                reset_at = first_msg_time + timedelta(hours=5)
            else:
                reset_at = datetime.now() + timedelta(hours=5)

            return RateLimitState(
                requests_used=messages_sent // 2,  # Roughly half are user messages
                requests_limit=default_limit,
                reset_at=reset_at,
                window_type=WindowType.FIVE_HOUR,
            )
        except Exception as e:
            logger.debug(f"Error parsing Claude session: {e}")
            return None

    def get_session_state(self) -> Optional[SessionState]:
        """Parse Claude Code session state."""
        projects_dir = self.state_dir / "projects"
        if not projects_dir.exists():
            return None

        # Find most recent session
        latest_session = None
        latest_mtime = 0

        for project_dir in projects_dir.iterdir():
            if not project_dir.is_dir():
                continue
            session_file = self._get_most_recent_file(project_dir, "*.jsonl")
            if session_file and session_file.stat().st_mtime > latest_mtime:
                latest_session = session_file
                latest_mtime = session_file.stat().st_mtime

        if not latest_session:
            return None

        try:
            stat = latest_session.stat()

            # Read last few lines to check for waiting state
            with open(latest_session) as f:
                lines = f.readlines()

            last_line = lines[-1] if lines else ""
            waiting_for_input = False
            waiting_state = WaitingState.IDLE
            input_prompt = None

            # Check if last message indicates waiting for input
            if '"waiting_for_input":true' in last_line.lower():
                waiting_for_input = True
                waiting_state = WaitingState.APPROVAL

            return SessionState(
                session_id=latest_session.stem,
                started_at=datetime.fromtimestamp(stat.st_ctime),
                last_activity_at=datetime.fromtimestamp(stat.st_mtime),
                is_active=(datetime.now().timestamp() - stat.st_mtime) < 300,  # Active if updated < 5min
                waiting_for_input=waiting_for_input,
                waiting_state=waiting_state,
                input_prompt=input_prompt,
                conversation_turns=len(lines),
            )
        except Exception as e:
            logger.debug(f"Error parsing Claude session state: {e}")
            return None


class CodexStateReader(CLIStateReader):
    """
    Read Codex CLI state files from ~/.codex/

    Codex stores:
    - Sessions in ~/.codex/sessions/
    - State in ~/.codex/state.json
    """

    CLI_TYPE = "codex"

    def _get_default_dir(self) -> Path:
        return Path("~/.codex").expanduser()

    def is_installed(self) -> bool:
        """Check if Codex CLI is installed."""
        import shutil
        return shutil.which("codex") is not None or self.exists()

    def get_rate_limit_state(self) -> Optional[RateLimitState]:
        """Parse Codex CLI rate limits."""
        state_file = self.state_dir / "state.json"
        data = self._read_json_file(state_file)

        if not data:
            return None

        try:
            usage = data.get("usage", {})
            limits = data.get("limits", {})

            return RateLimitState(
                requests_used=usage.get("messages_sent", 0),
                requests_limit=limits.get("messages_per_3h", 80),
                reset_at=datetime.fromisoformat(
                    usage.get("reset_at", (datetime.now() + timedelta(hours=3)).isoformat())
                ),
                window_type=WindowType.THREE_HOUR,
            )
        except Exception as e:
            logger.debug(f"Error parsing Codex rate limits: {e}")
            return None

    def get_session_state(self) -> Optional[SessionState]:
        """Parse Codex session state."""
        sessions_dir = self.state_dir / "sessions"
        if not sessions_dir.exists():
            return None

        latest = self._get_most_recent_file(sessions_dir, "*.json")
        if not latest:
            return None

        data = self._read_json_file(latest)
        if not data:
            return None

        try:
            stat = latest.stat()

            return SessionState(
                session_id=data.get("session_id", latest.stem),
                started_at=datetime.fromisoformat(
                    data.get("started_at", datetime.fromtimestamp(stat.st_ctime).isoformat())
                ),
                last_activity_at=datetime.fromtimestamp(stat.st_mtime),
                is_active=data.get("is_active", False),
                waiting_for_input=data.get("waiting_for_input", False),
                waiting_state=WaitingState(data.get("waiting_state", "idle")),
                input_prompt=data.get("pending_prompt"),
                current_task=data.get("current_task"),
            )
        except Exception as e:
            logger.debug(f"Error parsing Codex session: {e}")
            return None


class GeminiStateReader(CLIStateReader):
    """
    Read Gemini CLI state files from ~/.gemini/

    Gemini stores:
    - Config in ~/.gemini/config.yaml
    - Temp files in ~/.gemini/tmp/
    """

    CLI_TYPE = "gemini"

    def _get_default_dir(self) -> Path:
        return Path("~/.gemini").expanduser()

    def is_installed(self) -> bool:
        """Check if Gemini CLI is installed."""
        import shutil
        return shutil.which("gemini") is not None or self.exists()

    def get_rate_limit_state(self) -> Optional[RateLimitState]:
        """Parse Gemini CLI rate limits."""
        # Try reading from config or tmp state
        config_file = self.state_dir / "config.yaml"
        data = self._read_yaml_file(config_file)

        # Gemini Pro typically has generous limits
        default_limit = 1000  # Gemini Pro daily limit

        if not data:
            # Return default if no config
            return RateLimitState(
                requests_used=0,
                requests_limit=default_limit,
                reset_at=datetime.now().replace(hour=0, minute=0, second=0) + timedelta(days=1),
                window_type=WindowType.DAILY,
            )

        try:
            usage = data.get("usage", {})
            limits = data.get("limits", {})

            return RateLimitState(
                requests_used=usage.get("daily_messages", 0),
                requests_limit=limits.get("daily_limit", default_limit),
                reset_at=datetime.now().replace(hour=0, minute=0, second=0) + timedelta(days=1),
                window_type=WindowType.DAILY,
            )
        except Exception as e:
            logger.debug(f"Error parsing Gemini rate limits: {e}")
            return None

    def get_session_state(self) -> Optional[SessionState]:
        """Parse Gemini session state."""
        tmp_dir = self.state_dir / "tmp"
        if not tmp_dir.exists():
            return None

        latest = self._get_most_recent_file(tmp_dir, "session_*.json")
        if not latest:
            return None

        data = self._read_json_file(latest)
        if not data:
            return None

        try:
            stat = latest.stat()

            return SessionState(
                session_id=data.get("session_id", latest.stem),
                started_at=datetime.fromisoformat(
                    data.get("started_at", datetime.fromtimestamp(stat.st_ctime).isoformat())
                ),
                last_activity_at=datetime.fromtimestamp(stat.st_mtime),
                is_active=data.get("is_active", False),
                waiting_for_input=data.get("waiting_for_input", False),
                waiting_state=WaitingState(data.get("waiting_state", "idle")),
                input_prompt=data.get("pending_prompt"),
                current_task=data.get("current_task"),
            )
        except Exception as e:
            logger.debug(f"Error parsing Gemini session: {e}")
            return None


# Registry of available readers
_READER_REGISTRY: dict[str, type[CLIStateReader]] = {
    "claude": ClaudeStateReader,
    "codex": CodexStateReader,
    "gemini": GeminiStateReader,
}


def get_reader_for_agent(agent_type: str) -> Optional[CLIStateReader]:
    """
    Get a state reader for a specific agent type.

    Args:
        agent_type: Type of agent ("claude", "codex", "gemini")

    Returns:
        CLIStateReader instance or None if not supported
    """
    reader_class = _READER_REGISTRY.get(agent_type.lower())
    if reader_class:
        return reader_class()
    return None


def get_all_readers() -> dict[str, CLIStateReader]:
    """
    Get readers for all supported CLI agents.

    Returns:
        Dict mapping agent type to reader instance
    """
    return {name: cls() for name, cls in _READER_REGISTRY.items()}


def register_reader(agent_type: str, reader_class: type[CLIStateReader]) -> None:
    """
    Register a custom state reader.

    Args:
        agent_type: Type identifier for the agent
        reader_class: CLIStateReader subclass
    """
    _READER_REGISTRY[agent_type.lower()] = reader_class
