"""
Formatter Tool - Format code using language-specific formatters

Supports per-language formatters:
- Python: black, ruff, autopep8, yapf
- JavaScript/TypeScript: prettier, eslint
- Rust: rustfmt
- Go: gofmt
- JSON/YAML/Markdown: prettier
- And more...

Example:
    tool = FormatterTool()

    # Format a file
    result = await tool.execute(
        path="/project/src/main.py",
        formatter="black",
    )

    # Auto-detect formatter
    result = await tool.execute(
        path="/project/src/app.ts",
    )
"""

import asyncio
import shutil
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Optional

from groknroll.tools.base_tool import BaseTool


class FormatterType(Enum):
    """Types of formatters"""

    BLACK = "black"
    RUFF = "ruff"
    AUTOPEP8 = "autopep8"
    YAPF = "yapf"
    PRETTIER = "prettier"
    ESLINT = "eslint"
    RUSTFMT = "rustfmt"
    GOFMT = "gofmt"
    CLANG_FORMAT = "clang-format"
    SHFMT = "shfmt"
    CUSTOM = "custom"


@dataclass
class FormatterConfig:
    """
    Configuration for a formatter

    Attributes:
        name: Formatter name
        command: Command to run
        args: Additional arguments
        extensions: File extensions this formatter handles
        config_file: Optional config file path
    """

    name: str
    command: str
    args: list[str] = field(default_factory=list)
    extensions: list[str] = field(default_factory=list)
    config_file: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "name": self.name,
            "command": self.command,
            "args": self.args,
            "extensions": self.extensions,
            "config_file": self.config_file,
        }


@dataclass
class FormatResult:
    """
    Result of formatting operation

    Attributes:
        path: Path to the formatted file
        formatter: Formatter that was used
        success: Whether formatting succeeded
        changed: Whether the file was changed
        original_content: Original file content
        formatted_content: Formatted file content
        error: Error message if failed
        stdout: Formatter stdout
        stderr: Formatter stderr
    """

    path: Path
    formatter: str
    success: bool
    changed: bool = False
    original_content: Optional[str] = None
    formatted_content: Optional[str] = None
    error: Optional[str] = None
    stdout: str = ""
    stderr: str = ""


# Default formatter configurations
DEFAULT_FORMATTERS: dict[str, FormatterConfig] = {
    "black": FormatterConfig(
        name="black",
        command="black",
        args=["--quiet", "-"],
        extensions=[".py"],
    ),
    "ruff": FormatterConfig(
        name="ruff",
        command="ruff",
        args=["format", "-"],
        extensions=[".py"],
    ),
    "autopep8": FormatterConfig(
        name="autopep8",
        command="autopep8",
        args=["-"],
        extensions=[".py"],
    ),
    "yapf": FormatterConfig(
        name="yapf",
        command="yapf",
        args=[],
        extensions=[".py"],
    ),
    "prettier": FormatterConfig(
        name="prettier",
        command="prettier",
        args=["--stdin-filepath"],
        extensions=[
            ".js",
            ".jsx",
            ".ts",
            ".tsx",
            ".json",
            ".yaml",
            ".yml",
            ".md",
            ".css",
            ".scss",
            ".html",
        ],
    ),
    "eslint": FormatterConfig(
        name="eslint",
        command="eslint",
        args=["--fix", "--stdin"],
        extensions=[".js", ".jsx", ".ts", ".tsx"],
    ),
    "rustfmt": FormatterConfig(
        name="rustfmt",
        command="rustfmt",
        args=[],
        extensions=[".rs"],
    ),
    "gofmt": FormatterConfig(
        name="gofmt",
        command="gofmt",
        args=[],
        extensions=[".go"],
    ),
    "clang-format": FormatterConfig(
        name="clang-format",
        command="clang-format",
        args=["-i"],
        extensions=[".c", ".cpp", ".h", ".hpp", ".cc", ".cxx"],
    ),
    "shfmt": FormatterConfig(
        name="shfmt",
        command="shfmt",
        args=["-i", "2", "-w"],
        extensions=[".sh", ".bash"],
    ),
}

# Default formatter by extension
DEFAULT_FORMATTER_BY_EXT: dict[str, str] = {
    ".py": "black",
    ".pyi": "black",
    ".js": "prettier",
    ".jsx": "prettier",
    ".ts": "prettier",
    ".tsx": "prettier",
    ".json": "prettier",
    ".yaml": "prettier",
    ".yml": "prettier",
    ".md": "prettier",
    ".css": "prettier",
    ".scss": "prettier",
    ".html": "prettier",
    ".rs": "rustfmt",
    ".go": "gofmt",
    ".c": "clang-format",
    ".cpp": "clang-format",
    ".h": "clang-format",
    ".hpp": "clang-format",
    ".sh": "shfmt",
    ".bash": "shfmt",
}


class FormatterRegistry:
    """
    Registry for formatters

    Manages formatter configurations and provides lookup by extension.
    """

    def __init__(self):
        """Initialize registry with default formatters"""
        self._formatters: dict[str, FormatterConfig] = dict(DEFAULT_FORMATTERS)
        self._ext_mapping: dict[str, str] = dict(DEFAULT_FORMATTER_BY_EXT)

    def register(self, config: FormatterConfig) -> None:
        """
        Register a formatter

        Args:
            config: Formatter configuration
        """
        self._formatters[config.name] = config
        for ext in config.extensions:
            self._ext_mapping[ext] = config.name

    def get(self, name: str) -> Optional[FormatterConfig]:
        """
        Get formatter by name

        Args:
            name: Formatter name

        Returns:
            FormatterConfig or None
        """
        return self._formatters.get(name)

    def get_for_extension(self, ext: str) -> Optional[FormatterConfig]:
        """
        Get formatter for a file extension

        Args:
            ext: File extension (with dot)

        Returns:
            FormatterConfig or None
        """
        formatter_name = self._ext_mapping.get(ext.lower())
        if formatter_name:
            return self._formatters.get(formatter_name)
        return None

    def get_for_file(self, path: Path) -> Optional[FormatterConfig]:
        """
        Get formatter for a file

        Args:
            path: File path

        Returns:
            FormatterConfig or None
        """
        return self.get_for_extension(path.suffix)

    def set_default_for_extension(self, ext: str, formatter_name: str) -> None:
        """
        Set default formatter for an extension

        Args:
            ext: File extension (with dot)
            formatter_name: Name of formatter to use
        """
        self._ext_mapping[ext.lower()] = formatter_name

    def list_formatters(self) -> list[FormatterConfig]:
        """List all registered formatters"""
        return list(self._formatters.values())

    def list_supported_extensions(self) -> list[str]:
        """List all supported file extensions"""
        return list(self._ext_mapping.keys())


class Formatter:
    """
    Code formatter

    Formats code using configured formatters. Supports:
    - Formatting via stdin/stdout
    - Formatting files in place
    - Dry-run mode to preview changes
    """

    def __init__(self, registry: Optional[FormatterRegistry] = None):
        """
        Initialize Formatter

        Args:
            registry: Formatter registry (defaults to default registry)
        """
        self.registry = registry or FormatterRegistry()

    async def format_content(
        self,
        content: str,
        formatter_name: str,
        filename: Optional[str] = None,
    ) -> FormatResult:
        """
        Format content using a formatter

        Args:
            content: Content to format
            formatter_name: Name of formatter to use
            filename: Optional filename for context

        Returns:
            FormatResult with formatted content
        """
        config = self.registry.get(formatter_name)
        if not config:
            return FormatResult(
                path=Path(filename or "unknown"),
                formatter=formatter_name,
                success=False,
                error=f"Unknown formatter: {formatter_name}",
            )

        # Check if formatter is available
        if not shutil.which(config.command):
            return FormatResult(
                path=Path(filename or "unknown"),
                formatter=formatter_name,
                success=False,
                error=f"Formatter not installed: {config.command}",
            )

        # Build command
        args = [config.command] + config.args

        # Some formatters need the filename for proper formatting
        if formatter_name == "prettier" and filename:
            args.append(filename)

        try:
            # Run formatter
            process = await asyncio.create_subprocess_exec(
                *args,
                stdin=asyncio.subprocess.PIPE,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )

            stdout, stderr = await process.communicate(content.encode())

            if process.returncode != 0:
                return FormatResult(
                    path=Path(filename or "unknown"),
                    formatter=formatter_name,
                    success=False,
                    original_content=content,
                    error=stderr.decode()
                    if stderr
                    else f"Formatter exited with code {process.returncode}",
                    stdout=stdout.decode() if stdout else "",
                    stderr=stderr.decode() if stderr else "",
                )

            formatted = stdout.decode()

            return FormatResult(
                path=Path(filename or "unknown"),
                formatter=formatter_name,
                success=True,
                changed=formatted != content,
                original_content=content,
                formatted_content=formatted,
                stdout=stdout.decode() if stdout else "",
                stderr=stderr.decode() if stderr else "",
            )

        except Exception as e:
            return FormatResult(
                path=Path(filename or "unknown"),
                formatter=formatter_name,
                success=False,
                original_content=content,
                error=str(e),
            )

    async def format_file(
        self,
        path: Path,
        formatter_name: Optional[str] = None,
        write: bool = True,
    ) -> FormatResult:
        """
        Format a file

        Args:
            path: Path to file
            formatter_name: Formatter to use (auto-detect if None)
            write: Whether to write changes to file

        Returns:
            FormatResult with formatted content
        """
        path = Path(path).resolve()

        if not path.exists():
            return FormatResult(
                path=path,
                formatter=formatter_name or "unknown",
                success=False,
                error="File not found",
            )

        # Auto-detect formatter if not specified
        if not formatter_name:
            config = self.registry.get_for_file(path)
            if config:
                formatter_name = config.name
            else:
                return FormatResult(
                    path=path,
                    formatter="unknown",
                    success=False,
                    error=f"No formatter for extension: {path.suffix}",
                )

        # Read file
        try:
            content = path.read_text()
        except Exception as e:
            return FormatResult(
                path=path,
                formatter=formatter_name,
                success=False,
                error=f"Failed to read file: {e}",
            )

        # Format content
        result = await self.format_content(
            content=content,
            formatter_name=formatter_name,
            filename=str(path),
        )

        result.path = path

        # Write if requested and successful
        if write and result.success and result.changed and result.formatted_content:
            try:
                path.write_text(result.formatted_content)
            except Exception as e:
                result.success = False
                result.error = f"Failed to write file: {e}"

        return result

    def is_formatter_available(self, formatter_name: str) -> bool:
        """
        Check if a formatter is available

        Args:
            formatter_name: Name of formatter

        Returns:
            True if formatter is installed
        """
        config = self.registry.get(formatter_name)
        if not config:
            return False
        return shutil.which(config.command) is not None

    def get_available_formatters(self) -> list[str]:
        """
        Get list of available (installed) formatters

        Returns:
            List of formatter names
        """
        available = []
        for config in self.registry.list_formatters():
            if shutil.which(config.command):
                available.append(config.name)
        return available


class FormatterTool(BaseTool):
    """
    Tool for formatting code

    Provides code formatting capabilities integrated with the tool system.
    """

    def __init__(self, registry: Optional[FormatterRegistry] = None):
        """
        Initialize FormatterTool

        Args:
            registry: Formatter registry
        """
        self._formatter = Formatter(registry)

    @property
    def name(self) -> str:
        return "format"

    @property
    def description(self) -> str:
        return "Format code using language-specific formatters"

    async def execute(
        self,
        path: str,
        formatter: Optional[str] = None,
        write: bool = True,
        content: Optional[str] = None,
    ) -> FormatResult:
        """
        Format a file or content

        Args:
            path: Path to file (or filename for content mode)
            formatter: Formatter to use (auto-detect if None)
            write: Whether to write changes to file
            content: Content to format (if provided, formats content instead of file)

        Returns:
            FormatResult with formatted content
        """
        if content is not None:
            # Format content directly
            if not formatter:
                # Auto-detect from path extension
                config = self._formatter.registry.get_for_file(Path(path))
                if config:
                    formatter = config.name
                else:
                    return FormatResult(
                        path=Path(path),
                        formatter="unknown",
                        success=False,
                        error=f"No formatter for extension: {Path(path).suffix}",
                    )

            return await self._formatter.format_content(
                content=content,
                formatter_name=formatter,
                filename=path,
            )
        else:
            # Format file
            return await self._formatter.format_file(
                path=Path(path),
                formatter_name=formatter,
                write=write,
            )

    def list_formatters(self) -> list[str]:
        """List available formatters"""
        return self._formatter.get_available_formatters()

    def is_available(self, formatter: str) -> bool:
        """Check if a formatter is available"""
        return self._formatter.is_formatter_available(formatter)


# Global registry instance
_formatter_registry: Optional[FormatterRegistry] = None


def get_formatter_registry() -> FormatterRegistry:
    """Get the global formatter registry"""
    global _formatter_registry
    if _formatter_registry is None:
        _formatter_registry = FormatterRegistry()
    return _formatter_registry


def reset_formatter_registry() -> None:
    """Reset the global formatter registry (for testing)"""
    global _formatter_registry
    _formatter_registry = None
