"""File I/O operations with a configurable working directory."""

import os
from pathlib import Path
from typing import Optional

from agentscope.message import TextBlock
from agentscope.tool import ToolResponse

from .utils import DEFAULT_MAX_BYTES, read_file_safe, truncate_output


class FileIO:
    """File I/O operations with a configurable working directory."""

    def __init__(self, working_dir: str | Path):
        """Initialize FileIO with a working directory.

        Args:
            working_dir (`str`):
                The working directory for resolving relative paths.
        """
        self.working_dir = Path(working_dir)

    def _resolve_file_path(self, file_path: str) -> str:
        """Resolve file path: use absolute path as-is,
        resolve relative path from working_dir.

        Args:
            file_path: The input file path (absolute or relative).

        Returns:
            The resolved absolute file path as string.
        """
        path = Path(file_path)
        if path.is_absolute():
            return str(path)
        else:
            return str(self.working_dir / file_path)

    async def read(  # pylint: disable=too-many-return-statements
        self,
        file_path: str,
        start_line: Optional[int] = None,
        end_line: Optional[int] = None,
    ) -> ToolResponse:
        """Read a file. Relative paths resolve from working_dir.

        Use start_line/end_line to read a specific line range (output includes
        line numbers). Omit both to read the full file.

        Args:
            file_path (`str`):
                Path to the file.
            start_line (`int`, optional):
                First line to read (1-based, inclusive).
            end_line (`int`, optional):
                Last line to read (1-based, inclusive).
        """
        file_path = self._resolve_file_path(file_path)

        if not os.path.exists(file_path):
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Error: The file {file_path} does not exist.",
                    ),
                ],
            )

        if not os.path.isfile(file_path):
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Error: The path {file_path} is not a file.",
                    ),
                ],
            )

        try:
            content = read_file_safe(file_path)
            all_lines = content.split("\n")
            total = len(all_lines)

            # Determine read range
            s = max(1, start_line if start_line is not None else 1)
            e = min(total, end_line if end_line is not None else total)

            if s > total:
                return ToolResponse(
                    content=[
                        TextBlock(
                            type="text",
                            text=f"Error: start_line {s} exceeds file length ({total} lines).",
                        ),
                    ],
                )

            if s > e:
                return ToolResponse(
                    content=[
                        TextBlock(
                            type="text",
                            text=f"Error: start_line ({s}) > end_line ({e}).",
                        ),
                    ],
                )

            # Extract selected lines
            selected_content = "\n".join(all_lines[s - 1 : e])

            # Apply smart truncation (keep head for file reading)
            truncated, was_truncated, output_lines, reason = truncate_output(selected_content, keep="head")

            # Build response with truncation hints
            if was_truncated:
                end_display = s + output_lines - 1
                next_line = end_display + 1
                if reason == "lines":
                    hint = f"\n\n[Lines {s}-{end_display} of {total}. Use start_line={next_line} to continue.]"
                else:
                    hint = (
                        f"\n\n[Lines {s}-{end_display} of {total} ({DEFAULT_MAX_BYTES // 1024}KB limit). "
                        f"Use start_line={next_line} to continue.]"
                    )
                text = truncated + hint
            elif e < total:
                remaining = total - e
                text = (
                    f"{file_path}  (lines {s}-{e} of {total})\n{truncated}\n\n[{remaining} more lines. "
                    f"Use start_line={e + 1} to continue.]"
                )
            else:
                text = truncated

            return ToolResponse(
                content=[TextBlock(type="text", text=text)],
            )

        except Exception as e:
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Error: Read file failed due to \n{e}",
                    ),
                ],
            )

    async def write(
        self,
        file_path: str,
        content: str,
    ) -> ToolResponse:
        """Create or overwrite a file. Relative paths resolve from working_dir.

        Args:
            file_path (`str`):
                Path to the file.
            content (`str`):
                Content to write.
        """
        if not file_path:
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text="Error: No `file_path` provide.",
                    ),
                ],
            )

        file_path = self._resolve_file_path(file_path)

        try:
            with open(file_path, "w", encoding="utf-8") as file:
                file.write(content)
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Wrote {len(content)} bytes to {file_path}.",
                    ),
                ],
            )
        except Exception as e:
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Error: Write file failed due to \n{e}",
                    ),
                ],
            )

    async def edit(
        self,
        file_path: str,
        old_text: str,
        new_text: str,
    ) -> ToolResponse:
        """Find-and-replace text in a file. All occurrences of old_text are
        replaced with new_text. Relative paths resolve from working_dir.

        Args:
            file_path (`str`):
                Path to the file.
            old_text (`str`):
                Exact text to find.
            new_text (`str`):
                Replacement text.
        """
        response = await self.read(file_path=file_path)
        if response.content and len(response.content) > 0:
            error_text = response.content[0].get("text", "")
            if error_text.startswith("Error:"):
                return response
        if not response.content or len(response.content) == 0:
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Error: Failed to read file {file_path}.",
                    ),
                ],
            )

        content = response.content[0].get("text", "")
        if old_text not in content:
            return ToolResponse(
                content=[
                    TextBlock(
                        type="text",
                        text=f"Error: The text to replace was not found in {file_path}.",
                    ),
                ],
            )

        new_content = content.replace(old_text, new_text)
        write_response = await self.write(file_path=file_path, content=new_content)

        if write_response.content and len(write_response.content) > 0:
            write_text = write_response.content[0].get("text", "")
            if write_text.startswith("Error:"):
                return write_response

        return ToolResponse(
            content=[
                TextBlock(
                    type="text",
                    text=f"Successfully replaced text in {file_path}.",
                ),
            ],
        )
