"""
ListTool - List directory contents with details

Following OpenCode architecture, provides directory listing with:
- File/directory details (size, modification time, type)
- Recursive listing support
- Pattern filtering
- Sorted results
- Type-safe implementation
"""

from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any, Optional

from groknroll.tools.base_tool import BaseTool


@dataclass
class ListEntry:
    """Represents a file or directory entry"""

    name: str
    path: str
    is_dir: bool
    size: int
    modified: datetime
    permissions: str

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "name": self.name,
            "path": self.path,
            "is_dir": self.is_dir,
            "size": self.size,
            "modified": self.modified.isoformat(),
            "permissions": self.permissions,
            "type": "directory" if self.is_dir else "file",
        }

    def format_line(self) -> str:
        """Format as a single line for display"""
        type_char = "d" if self.is_dir else "-"
        size_str = self._format_size()
        time_str = self.modified.strftime("%Y-%m-%d %H:%M")
        return f"{type_char}{self.permissions} {size_str:>10} {time_str} {self.name}"

    def _format_size(self) -> str:
        """Format size in human-readable format"""
        if self.is_dir:
            return "<DIR>"
        if self.size < 1024:
            return f"{self.size}B"
        elif self.size < 1024 * 1024:
            return f"{self.size / 1024:.1f}K"
        elif self.size < 1024 * 1024 * 1024:
            return f"{self.size / (1024 * 1024):.1f}M"
        else:
            return f"{self.size / (1024 * 1024 * 1024):.1f}G"


@dataclass
class ListResult:
    """Result of a list operation"""

    success: bool
    path: str
    entries: list[ListEntry]
    total_files: int
    total_dirs: int
    total_size: int
    error: Optional[str] = None

    def format_output(self) -> str:
        """Format as human-readable output"""
        if not self.success:
            return f"Error: {self.error}"

        lines = [f"Directory: {self.path}", ""]

        for entry in self.entries:
            lines.append(entry.format_line())

        lines.append("")
        lines.append(
            f"Total: {self.total_files} files, {self.total_dirs} directories, "
            f"{self._format_total_size()}"
        )

        return "\n".join(lines)

    def _format_total_size(self) -> str:
        """Format total size in human-readable format"""
        size = self.total_size
        if size < 1024:
            return f"{size} bytes"
        elif size < 1024 * 1024:
            return f"{size / 1024:.1f} KB"
        elif size < 1024 * 1024 * 1024:
            return f"{size / (1024 * 1024):.1f} MB"
        else:
            return f"{size / (1024 * 1024 * 1024):.1f} GB"


class ListTool(BaseTool):
    """
    Tool for listing directory contents with details

    Accepts:
        path: str - Directory path (absolute or relative)
        recursive: bool (optional) - List recursively (default: False)
        pattern: str (optional) - Filter by glob pattern
        max_depth: int (optional) - Maximum recursion depth (default: 3)
        show_hidden: bool (optional) - Show hidden files (default: False)

    Returns:
        ListResult - Directory listing with entries and statistics

    Raises:
        FileNotFoundError: If directory doesn't exist
        NotADirectoryError: If path is not a directory
        PermissionError: If directory can't be read

    Example:
        tool = ListTool()
        result = await tool.execute(path="src", recursive=True)
        print(result.format_output())
    """

    def __init__(self, workspace_root: Optional[str] = None):
        """
        Initialize ListTool

        Args:
            workspace_root: Optional root directory for relative paths.
                           Defaults to current working directory.
        """
        self._workspace_root = Path(workspace_root) if workspace_root else Path.cwd()

    @property
    def name(self) -> str:
        """Tool identifier"""
        return "list"

    @property
    def description(self) -> str:
        """Human-readable description"""
        return "List directory contents with details"

    def validate_params(self, **kwargs) -> dict[str, Any]:
        """
        Validate parameters before execution

        Args:
            **kwargs: May contain 'path', 'recursive', 'pattern', 'max_depth', 'show_hidden'

        Returns:
            Validated parameters dict

        Raises:
            ValueError: If parameters are invalid
        """
        # Path defaults to current directory
        path = kwargs.get("path", ".")
        if not isinstance(path, str):
            raise ValueError(f"path must be a string, got {type(path).__name__}")

        # Validate recursive flag
        recursive = kwargs.get("recursive", False)
        if not isinstance(recursive, bool):
            raise ValueError(f"recursive must be a boolean, got {type(recursive).__name__}")

        # Validate max_depth
        max_depth = kwargs.get("max_depth", 3)
        if not isinstance(max_depth, int):
            raise ValueError(f"max_depth must be an integer, got {type(max_depth).__name__}")
        if max_depth < 1:
            raise ValueError(f"max_depth must be at least 1, got {max_depth}")

        # Validate show_hidden
        show_hidden = kwargs.get("show_hidden", False)
        if not isinstance(show_hidden, bool):
            raise ValueError(f"show_hidden must be a boolean, got {type(show_hidden).__name__}")

        return {
            "path": path,
            "recursive": recursive,
            "pattern": kwargs.get("pattern"),
            "max_depth": max_depth,
            "show_hidden": show_hidden,
        }

    async def execute(self, **kwargs) -> ListResult:
        """
        Execute directory listing

        Args:
            **kwargs: Parameters for listing

        Returns:
            ListResult with directory contents

        Raises:
            FileNotFoundError: If directory doesn't exist
            NotADirectoryError: If path is not a directory
            PermissionError: If directory can't be read
        """
        # Validate parameters
        validated = self.validate_params(**kwargs)
        path_str = validated["path"]
        recursive = validated["recursive"]
        pattern = validated["pattern"]
        max_depth = validated["max_depth"]
        show_hidden = validated["show_hidden"]

        # Convert to Path object
        dir_path = Path(path_str)

        # Handle relative paths
        if not dir_path.is_absolute():
            dir_path = self._workspace_root / dir_path

        # Normalize path
        try:
            dir_path = dir_path.resolve()
        except (OSError, RuntimeError) as e:
            return ListResult(
                success=False,
                path=path_str,
                entries=[],
                total_files=0,
                total_dirs=0,
                total_size=0,
                error=f"Cannot resolve path: {e}",
            )

        # Check directory exists
        if not dir_path.exists():
            return ListResult(
                success=False,
                path=str(dir_path),
                entries=[],
                total_files=0,
                total_dirs=0,
                total_size=0,
                error=f"Directory not found: {dir_path}",
            )

        # Check it's a directory
        if not dir_path.is_dir():
            return ListResult(
                success=False,
                path=str(dir_path),
                entries=[],
                total_files=0,
                total_dirs=0,
                total_size=0,
                error=f"Path is not a directory: {dir_path}",
            )

        # List directory contents
        entries = []
        total_files = 0
        total_dirs = 0
        total_size = 0

        try:
            entries = self._list_directory(
                dir_path,
                recursive=recursive,
                pattern=pattern,
                max_depth=max_depth,
                show_hidden=show_hidden,
                current_depth=0,
            )

            # Calculate statistics
            for entry in entries:
                if entry.is_dir:
                    total_dirs += 1
                else:
                    total_files += 1
                    total_size += entry.size

            # Sort entries: directories first, then files, alphabetically
            entries.sort(key=lambda e: (not e.is_dir, e.name.lower()))

            return ListResult(
                success=True,
                path=str(dir_path),
                entries=entries,
                total_files=total_files,
                total_dirs=total_dirs,
                total_size=total_size,
            )

        except PermissionError:
            return ListResult(
                success=False,
                path=str(dir_path),
                entries=[],
                total_files=0,
                total_dirs=0,
                total_size=0,
                error=f"Permission denied: {dir_path}",
            )

    def _list_directory(
        self,
        dir_path: Path,
        recursive: bool,
        pattern: Optional[str],
        max_depth: int,
        show_hidden: bool,
        current_depth: int,
    ) -> list[ListEntry]:
        """
        List directory contents

        Args:
            dir_path: Directory to list
            recursive: Whether to recurse into subdirectories
            pattern: Optional glob pattern filter
            max_depth: Maximum recursion depth
            show_hidden: Whether to show hidden files
            current_depth: Current recursion depth

        Returns:
            List of ListEntry objects
        """
        entries = []

        try:
            for item in dir_path.iterdir():
                # Skip hidden files unless requested
                if not show_hidden and item.name.startswith("."):
                    continue

                # Skip ignored directories
                if self._should_ignore(item):
                    continue

                # Apply pattern filter if specified
                if pattern and not item.match(pattern):
                    # For directories, check if any children might match
                    if not (item.is_dir() and recursive):
                        continue

                # Get file stats
                try:
                    stat = item.stat()
                    modified = datetime.fromtimestamp(stat.st_mtime)
                    size = stat.st_size if not item.is_dir() else 0
                    permissions = self._format_permissions(stat.st_mode)
                except (OSError, PermissionError):
                    # Skip files we can't stat
                    continue

                # Create entry
                entry = ListEntry(
                    name=item.name,
                    path=str(item),
                    is_dir=item.is_dir(),
                    size=size,
                    modified=modified,
                    permissions=permissions,
                )
                entries.append(entry)

                # Recurse into subdirectories
                if recursive and item.is_dir() and current_depth < max_depth:
                    sub_entries = self._list_directory(
                        item,
                        recursive=True,
                        pattern=pattern,
                        max_depth=max_depth,
                        show_hidden=show_hidden,
                        current_depth=current_depth + 1,
                    )
                    entries.extend(sub_entries)

        except PermissionError:
            # Can't read directory
            pass

        return entries

    def _should_ignore(self, path: Path) -> bool:
        """Check if path should be ignored"""
        ignore_names = {
            ".git",
            "__pycache__",
            "node_modules",
            ".venv",
            "venv",
            ".pytest_cache",
            ".mypy_cache",
            ".ruff_cache",
            ".eggs",
        }
        return path.name in ignore_names

    def _format_permissions(self, mode: int) -> str:
        """Format file permissions as rwx string"""
        perms = ""
        for i in range(2, -1, -1):
            shift = i * 3
            perms += "r" if mode & (0o4 << shift) else "-"
            perms += "w" if mode & (0o2 << shift) else "-"
            perms += "x" if mode & (0o1 << shift) else "-"
        return perms
