"""
Gemini CLI Adapter - Wrapper for Google's Gemini CLI.

Gemini CLI is an open-source terminal agent with:
- ReAct loop with MCP server support
- 1M token context window
- Built-in tools (search, file ops, shell)
- Search grounding for current best practices
- 60 req/min, 1000 req/day free tier

Usage:
    from agent_orchestrator.adapters.gemini_cli import GeminiCLIAdapter

    adapter = GeminiCLIAdapter(
        agent_id="gemini-main",
        workspace_path="/path/to/repo",
    )
    response = await adapter.execute("Analyze this codebase", context)
"""

import asyncio
import json
import os
import shutil
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, AsyncIterator, Optional

from .base import (
    CLIAgentAdapter,
    AgentResponse,
    AgentStatus,
    PromptBuilder,
)
from .pricing import estimate_cost
from ..journal.status_packet import (
    StatusPacket,
    TaskArtifacts,
    create_success_packet,
    create_failed_packet,
)
from ..secrets.redactor import SecretRedactor


@dataclass
class GeminiCLIConfig:
    """Configuration for Gemini CLI adapter."""

    # Path to gemini executable
    executable: str = "gemini"

    # Timeout for execution in seconds
    timeout_seconds: int = 600  # 10 minutes

    # Working directory
    working_dir: Optional[str] = None

    # Model to use (default uses the CLI's default)
    model: Optional[str] = None

    # Enable search grounding
    search_grounding: bool = True

    # MCP servers to enable
    mcp_servers: list[str] = field(default_factory=list)

    # Additional CLI flags
    additional_flags: list[str] = field(default_factory=list)

    # Maximum context tokens (Gemini supports up to 1M)
    max_context_tokens: int = 100000

    # Auth check prompt (set to None to skip)
    auth_check_prompt: Optional[str] = "Auth check: respond with OK."

    # Auth check timeout in seconds
    auth_check_timeout_seconds: int = 30


class GeminiCLIAdapter(CLIAgentAdapter):
    """
    Adapter for Gemini CLI.

    Best suited for:
    - Large context analysis (1M token window)
    - Documentation tasks with search grounding
    - Tasks requiring current best practices lookup
    - MCP-heavy workflows

    Rate limits (free tier):
    - 60 requests per minute
    - 1000 requests per day
    """

    def __init__(
        self,
        agent_id: str,
        workspace_path: Optional[str] = None,
        config: Optional[GeminiCLIConfig] = None,
    ) -> None:
        """
        Initialize the Gemini CLI adapter.

        Args:
            agent_id: Unique identifier for this agent
            workspace_path: Path to the workspace
            config: Optional configuration overrides
        """
        super().__init__(agent_id, workspace_path)
        self.config = config or GeminiCLIConfig()
        self._last_packet: Optional[StatusPacket] = None
        self._process: Optional[asyncio.subprocess.Process] = None

        # Verify gemini is available
        self._gemini_available = self._check_gemini_available()

    def _check_gemini_available(self) -> bool:
        """Check if gemini CLI is available."""
        return shutil.which(self.config.executable) is not None

    async def execute(self, task: str, context: dict[str, Any]) -> AgentResponse:
        """
        Execute a task using Gemini CLI.

        Args:
            task: The task description/prompt
            context: Project state, constraints, etc.

        Returns:
            AgentResponse with result and usage metrics
        """
        if not self._gemini_available:
            return AgentResponse(
                content="",
                success=False,
                error="Gemini CLI not found in PATH. Install from: https://github.com/google-gemini/gemini-cli",
            )

        self._status = AgentStatus.RUNNING
        start_time = datetime.now()

        try:
            # Build the full prompt with context
            full_prompt = self._build_prompt(task, context)

            # Build command
            cmd = self._build_command(full_prompt)

            # Set working directory
            cwd = self.config.working_dir or self.workspace_path or os.getcwd()

            # Execute
            result = await self._run_command(cmd, cwd)

            # Parse response
            response = self._parse_response(result, task)

            # Update usage stats
            self._usage_stats.add_response(response)

            # Create status packet
            self._last_packet = create_success_packet(
                agent_id=self.agent_id,
                task_id=self._current_task_id or "",
                summary=f"Completed: {task[:100]}...",
                files_modified=response.artifacts.files_modified,
                next_steps=["Review output"],
            )
            self._last_packet.artifacts = response.artifacts

            self._status = AgentStatus.IDLE
            return response

        except asyncio.TimeoutError:
            error_msg = f"Gemini CLI timed out after {self.config.timeout_seconds}s"
            self._last_packet = create_failed_packet(
                agent_id=self.agent_id,
                task_id=self._current_task_id or "",
                error_message=error_msg,
            )
            self._status = AgentStatus.IDLE
            self._usage_stats.errors_count += 1

            return AgentResponse(
                content="",
                success=False,
                error=error_msg,
            )

        except Exception as e:
            error_msg = SecretRedactor.redact(str(e))
            self._last_packet = create_failed_packet(
                agent_id=self.agent_id,
                task_id=self._current_task_id or "",
                error_message=error_msg,
            )
            self._status = AgentStatus.IDLE
            self._usage_stats.errors_count += 1

            return AgentResponse(
                content="",
                success=False,
                error=error_msg,
            )

    async def stream(self, task: str, context: dict[str, Any]) -> AsyncIterator[str]:
        """
        Stream response from Gemini CLI.

        Args:
            task: The task description
            context: Additional context

        Yields:
            Response chunks as they become available
        """
        if not self._gemini_available:
            yield "Error: Gemini CLI not found in PATH"
            return

        self._status = AgentStatus.RUNNING

        try:
            full_prompt = self._build_prompt(task, context)
            cmd = self._build_command(full_prompt)
            cwd = self.config.working_dir or self.workspace_path or os.getcwd()

            # Create subprocess
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
            )

            # Stream output line by line
            async for line in process.stdout:
                line_str = line.decode("utf-8")
                if line_str.strip():
                    yield SecretRedactor.redact(line_str)

            await process.wait()

        except Exception as e:
            yield f"Error: {SecretRedactor.redact(str(e))}"

        finally:
            self._status = AgentStatus.IDLE

    def _build_prompt(self, task: str, context: dict[str, Any]) -> str:
        """Build the full prompt with context injection."""
        builder = PromptBuilder.for_cli_agent()

        # Add Gemini-specific note about search grounding
        additional_sections = None
        if self.config.search_grounding:
            additional_sections = {
                "Note": "Use search grounding to find current best practices when relevant."
            }

        return builder.build(
            task,
            context,
            output_requirements=PromptBuilder.minimal_output_requirements(),
            additional_sections=additional_sections,
        )

    def _build_command(self, prompt: str) -> list[str]:
        """Build the gemini CLI command."""
        cmd = [self.config.executable]

        # Add prompt (using -p or similar flag depending on gemini CLI version)
        # Note: Actual flags depend on the specific gemini CLI implementation
        cmd.extend(["--prompt", prompt])

        # Add model if specified
        if self.config.model:
            cmd.extend(["--model", self.config.model])

        # Add MCP servers
        for server in self.config.mcp_servers:
            cmd.extend(["--mcp-server", server])

        # Add additional flags
        cmd.extend(self.config.additional_flags)

        return cmd

    async def _run_command(
        self,
        cmd: list[str],
        cwd: str,
        timeout_seconds: Optional[int] = None,
    ) -> dict[str, Any]:
        """Run the gemini command and return parsed output."""
        process = await asyncio.create_subprocess_exec(
            *cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            cwd=cwd,
        )

        try:
            stdout, stderr = await asyncio.wait_for(
                process.communicate(),
                timeout=timeout_seconds or self.config.timeout_seconds,
            )
        except asyncio.TimeoutError:
            process.kill()
            await process.wait()
            raise

        stdout_str = stdout.decode("utf-8")
        stderr_str = stderr.decode("utf-8")

        if process.returncode != 0:
            error = stderr_str or stdout_str or f"Exit code {process.returncode}"
            raise RuntimeError(f"Gemini CLI failed: {SecretRedactor.redact(error)}")

        # Try to parse as JSON, otherwise return as content
        try:
            return json.loads(stdout_str)
        except json.JSONDecodeError:
            return {
                "content": stdout_str,
                "raw_output": True,
            }

    def _parse_response(self, result: dict[str, Any], task: str) -> AgentResponse:
        """Parse the response from Gemini CLI."""
        # Extract content
        content = ""
        if "content" in result:
            content = result["content"]
        elif "response" in result:
            content = result["response"]
        elif "text" in result:
            content = result["text"]

        # Extract usage (Gemini provides this differently)
        usage = result.get("usage", result.get("metadata", {}))
        tokens_input = usage.get("prompt_token_count", usage.get("input_tokens", 0))
        tokens_output = usage.get("candidates_token_count", usage.get("output_tokens", 0))

        # Gemini free tier has $0 cost, but we estimate for paid usage
        cost = 0.0
        if tokens_input:
            # Use centralized pricing
            cost = estimate_cost("gemini-cli", tokens_input, tokens_output)

        # Extract files modified
        files_modified = result.get("files_modified", [])

        # Build artifacts
        artifacts = TaskArtifacts(
            diff_summary=result.get("diff_summary", ""),
            files_modified=files_modified,
            tokens_input=tokens_input,
            tokens_output=tokens_output,
            cost_usd=cost,
        )

        return AgentResponse(
            content=SecretRedactor.redact(content),
            tokens_input=tokens_input,
            tokens_output=tokens_output,
            tokens_used=tokens_input + tokens_output,
            cost=cost,
            model="gemini-cli",
            artifacts=artifacts,
            success=True,
            metadata={
                "raw_output": result.get("raw_output", False),
                "search_grounding_used": result.get("grounding_metadata") is not None,
            },
        )

    def write_status_packet(self) -> StatusPacket:
        """Return the last status packet."""
        if self._last_packet:
            return self._last_packet

        return StatusPacket(
            agent_id=self.agent_id,
            task_id=self._current_task_id or "",
            status="idle",
            artifacts=TaskArtifacts(
                tokens_input=self._usage_stats.tokens_input,
                tokens_output=self._usage_stats.tokens_output,
                cost_usd=self._usage_stats.total_cost,
            ),
        )

    def is_healthy(self) -> bool:
        """Check if Gemini CLI is available."""
        return self._gemini_available and self._status != AgentStatus.TERMINATED

    async def check_authentication(self) -> bool:
        """Check whether Gemini CLI is authenticated."""
        if not self._gemini_available:
            self._authenticated = False
            self._last_auth_error = "Gemini CLI not found in PATH"
            return False

        if not self.config.auth_check_prompt:
            self._authenticated = True
            self._last_auth_error = None
            return True

        cwd = self.config.working_dir or self.workspace_path or os.getcwd()
        try:
            cmd = self._build_command(self.config.auth_check_prompt)
            await self._run_command(
                cmd,
                cwd,
                timeout_seconds=self.config.auth_check_timeout_seconds,
            )
            self._authenticated = True
            self._last_auth_error = None
        except Exception as exc:
            self._authenticated = False
            self._last_auth_error = SecretRedactor.redact(str(exc))
        return self._authenticated

    async def cancel(self) -> bool:
        """Cancel the current execution."""
        if self._process:
            try:
                self._process.terminate()
                await asyncio.wait_for(self._process.wait(), timeout=5)
            except asyncio.TimeoutError:
                self._process.kill()
            self._process = None

        self._status = AgentStatus.IDLE
        return True

    def get_name(self) -> str:
        """Get adapter name."""
        return "GeminiCLI"


def create_gemini_adapter(
    agent_id: str,
    workspace_path: str,
    search_grounding: bool = True,
    timeout_seconds: int = 600,
) -> GeminiCLIAdapter:
    """
    Factory function to create a Gemini CLI adapter.

    Args:
        agent_id: Unique identifier for the agent
        workspace_path: Path to the workspace
        search_grounding: Enable search grounding for current info
        timeout_seconds: Maximum execution time

    Returns:
        Configured GeminiCLIAdapter
    """
    config = GeminiCLIConfig(
        timeout_seconds=timeout_seconds,
        search_grounding=search_grounding,
    )
    return GeminiCLIAdapter(
        agent_id=agent_id,
        workspace_path=workspace_path,
        config=config,
    )
