"""Read tool for reading file contents."""

from pathlib import Path
from typing import Any, Optional

from oclawma.tools.base import (
    BaseTool,
    ToolExecutionError,
    ToolParameter,
    ToolResult,
    ToolSchema,
)


class ReadTool(BaseTool):
    """Tool for reading file contents.

    Reads files with configurable offset, limit, and encoding options.
    Includes safety checks for path traversal and large files.
    """

    DEFAULT_MAX_SIZE = 1024 * 1024  # 1MB max read
    DEFAULT_MAX_LINES = 1000  # Max lines to read at once

    def __init__(
        self,
        max_size: int = DEFAULT_MAX_SIZE,
        max_lines: int = DEFAULT_MAX_LINES,
        allowed_paths: Optional[list] = None,
    ) -> None:
        """Initialize the read tool.

        Args:
            max_size: Maximum file size in bytes to read
            max_lines: Maximum number of lines to read at once
            allowed_paths: Optional list of allowed path prefixes for security
        """
        self._max_size = max_size
        self._max_lines = max_lines
        self._allowed_paths = allowed_paths
        super().__init__()

    def _define_schema(self) -> ToolSchema:
        """Define the read tool schema."""
        return ToolSchema(
            name="read",
            description="Read the contents of a file.",
            parameters=[
                ToolParameter(
                    name="path",
                    description="Path to the file to read (absolute or relative)",
                    type="string",
                    required=True,
                ),
                ToolParameter(
                    name="offset",
                    description="Line number to start reading from (1-indexed, default: 1)",
                    type="integer",
                    required=False,
                    default=1,
                ),
                ToolParameter(
                    name="limit",
                    description=f"Maximum number of lines to read (default: {self._max_lines})",
                    type="integer",
                    required=False,
                ),
                ToolParameter(
                    name="encoding",
                    description="File encoding (default: utf-8)",
                    type="string",
                    required=False,
                    default="utf-8",
                ),
            ],
            returns="string",
            examples=[
                'read(path="/etc/os-release")',
                'read(path="README.md", limit=50)',
                'read(path="/var/log/syslog", offset=100, limit=20)',
                'read(path="file.txt", encoding="latin-1")',
            ],
        )

    async def execute(
        self,
        path: str,
        offset: int = 1,
        limit: Optional[int] = None,
        encoding: str = "utf-8",
    ) -> ToolResult:
        """Read a file's contents.

        Args:
            path: Path to the file
            offset: Starting line number (1-indexed)
            limit: Maximum lines to read
            encoding: File encoding

        Returns:
            ToolResult with file contents
        """
        # Resolve path
        try:
            file_path = self._resolve_path(path)
        except ValueError as e:
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=str(e),
                metadata={"path": path},
            )

        # Check if file exists
        if not file_path.exists():
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=f"File not found: {path}",
                metadata={"resolved_path": str(file_path)},
            )

        # Check if it's a file (not a directory)
        if not file_path.is_file():
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=f"Path is not a file: {path}",
                metadata={"resolved_path": str(file_path)},
            )

        # Check file size
        try:
            file_size = file_path.stat().st_size
            if file_size > self._max_size:
                return ToolResult(
                    tool_name=self.name,
                    success=False,
                    output="",
                    error_message=(
                        f"File too large ({file_size:,} bytes). "
                        f"Maximum size: {self._max_size:,} bytes"
                    ),
                    metadata={
                        "path": str(file_path),
                        "file_size": file_size,
                        "max_size": self._max_size,
                    },
                )
        except OSError as e:
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=f"Cannot access file: {e}",
                metadata={"path": str(file_path)},
            )

        # Apply default limit
        if limit is None:
            limit = self._max_lines

        # Validate offset
        if offset < 1:
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message="Offset must be 1 or greater",
                metadata={"offset": offset},
            )

        # Read the file
        try:
            content, metadata = self._read_file(
                file_path,
                offset=offset,
                limit=limit,
                encoding=encoding,
            )

            return ToolResult(
                tool_name=self.name,
                success=True,
                output=content,
                metadata=metadata,
            )

        except UnicodeDecodeError as e:
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=(
                    f"Cannot decode file with encoding '{encoding}'. "
                    f"Try a different encoding (e.g., 'latin-1', 'cp1252')"
                ),
                metadata={
                    "path": str(file_path),
                    "encoding": encoding,
                    "error": str(e),
                },
            )
        except PermissionError:
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=f"Permission denied: {path}",
                metadata={"path": str(file_path)},
            )
        except Exception as e:
            return ToolResult(
                tool_name=self.name,
                success=False,
                output="",
                error_message=f"Error reading file: {e}",
                metadata={"path": str(file_path)},
            )

    def _resolve_path(self, path: str) -> Path:
        """Resolve and validate a file path.

        Args:
            path: The path string

        Returns:
            Resolved Path object

        Raises:
            ValueError: If path is invalid or outside allowed paths
        """
        # Expand user home directory
        expanded = Path(path).expanduser()

        # If relative, resolve against current working directory
        if not expanded.is_absolute():
            expanded = expanded.resolve()

        # Security: Check allowed paths if configured
        if self._allowed_paths:
            is_allowed = any(
                expanded.is_relative_to(Path(allowed).expanduser().resolve())
                for allowed in self._allowed_paths
            )
            if not is_allowed:
                raise ValueError(f"Access denied: Path '{path}' is outside allowed directories")

        return expanded

    def _read_file(
        self,
        path: Path,
        offset: int,
        limit: int,
        encoding: str,
    ) -> tuple:
        """Read file contents with pagination.

        Args:
            path: Path to file
            offset: Starting line (1-indexed)
            limit: Maximum lines to read
            encoding: File encoding

        Returns:
            Tuple of (content, metadata)
        """
        lines = []
        current_line = 0
        total_lines = 0

        # First pass: count total lines if needed
        try:
            with open(path, encoding=encoding, errors="replace") as f:
                total_lines = sum(1 for _ in f)
        except Exception:
            total_lines = -1  # Unknown

        # Second pass: read requested lines
        with open(path, encoding=encoding, errors="replace") as f:
            for line in f:
                current_line += 1

                # Skip lines before offset
                if current_line < offset:
                    continue

                # Stop if we've read enough
                if len(lines) >= limit:
                    break

                lines.append(line.rstrip("\n").rstrip("\r"))

        # Join lines
        content = "\n".join(lines)

        # Determine if content was truncated
        actual_limit_reached = len(lines) >= limit and current_line < total_lines
        end_line = offset + len(lines) - 1

        metadata = {
            "path": str(path),
            "encoding": encoding,
            "offset": offset,
            "limit": limit,
            "lines_read": len(lines),
            "start_line": offset,
            "end_line": end_line,
            "total_lines": total_lines if total_lines >= 0 else None,
            "truncated": actual_limit_reached,
        }

        # Add continuation info if truncated
        if actual_limit_reached:
            metadata["next_offset"] = end_line + 1
            content += f"\n\n[... {total_lines - end_line} more lines. Use offset={end_line + 1} to continue ...]"

        return content, metadata

    def validate_params(self, params: dict[str, Any]) -> dict[str, Any]:
        """Validate read parameters.

        Args:
            params: Parameters to validate

        Returns:
            Validated parameters
        """
        validated = super().validate_params(params)

        # Ensure offset is at least 1
        if "offset" in validated:
            offset = validated["offset"]
            if isinstance(offset, str):
                try:
                    offset = int(offset)
                except ValueError:
                    raise ToolExecutionError(
                        f"Invalid offset value: {offset}",
                        tool_name=self.name,
                    ) from None
            validated["offset"] = max(1, offset)

        # Clamp limit
        if "limit" in validated and validated["limit"] is not None:
            limit = validated["limit"]
            if isinstance(limit, str):
                try:
                    limit = int(limit)
                except ValueError:
                    raise ToolExecutionError(
                        f"Invalid limit value: {limit}",
                        tool_name=self.name,
                    ) from None
            validated["limit"] = min(limit, self._max_lines)

        return validated
