"""
Built-in Filesystem Tools

Provides file operations for agents.
"""

import logging
from typing import Dict, Any, List
from pathlib import Path

# Try to import CRS Lib for advanced file reading (PDF, DOCX, ZIP, etc.)
try:
    from crs.crs_lib import read_file_content
except ImportError:
    read_file_content = None

from ..base import (
    BaseTool, ToolMetadata, ToolParameter, ToolExecutionContext,
    ToolResult, ToolPermission
)

logger = logging.getLogger(__name__)


class WriteFileTool(BaseTool):
    """Write content to file"""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="WRITE_FILE",
            category="filesystem",
            description="Create or overwrite file with content",
            parameters=[
                ToolParameter(
                    name="path",
                    type="path",
                    required=True,
                    description="File path relative to workspace"
                ),
                ToolParameter(
                    name="content",
                    type="string",
                    required=True,
                    description="File content"
                ),
                ToolParameter(
                    name="create_dirs",
                    type="bool",
                    required=False,
                    default=True,
                    description="Create parent directories if needed"
                )
            ],
            permissions=[ToolPermission.WRITE],
            examples=[
                '{"name":"WRITE_FILE","parameters":{"path":"demo.txt","content":"Hello World"}}'
            ]
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        raw_path = params.get("path") or params.get("file", "")
        content = params.get("content") or params.get("text", "")
        create_dirs = params.get("create_dirs", True)

        # Determine base directory: always use workspace (no cwd until CD is implemented)
        workspace = Path(context.workspace_path)
        
        # P1.3: Path Auto-Correction (DWIM for Local Models)
        clean_path = raw_path

        # If the path is already an absolute path inside the workspace, just make it relative
        try:
            resolved = Path(raw_path).resolve()
            rel = resolved.relative_to(workspace.resolve())
            clean_path = str(rel)
        except (ValueError, OSError):
            # Not inside workspace — apply standard sanitization
            # Strip common hallucinated prefixes
            bad_prefixes = [
                '/home/user/workspace/', '/home/user/', '/tmp/', 'C:\\Users\\User\\',
                'current/directory/', '/current/directory/'
            ]
            
            for prefix in bad_prefixes:
                if clean_path.startswith(prefix) or clean_path.lower().startswith(prefix.lower()):
                    clean_path = clean_path[len(prefix):]
            
            # Force relative to avoid pathlib replacement behavior on Windows (C:/...)
            clean_path = clean_path.lstrip(r'\/')
            
            # Resolve target - if clean_path starts with a drive letter, pathlib will reset anchor
            if ":" in clean_path:
                # Heuristic: strip up to last colon
                clean_path = clean_path.split(":")[-1].lstrip(r'\/')
        
        target = (workspace / clean_path).resolve()

        if str(clean_path) != str(raw_path):
             logger.info(f"[WRITE_FILE] Path sanitized: '{raw_path}' -> '{clean_path}'")

        # Security: must be within workspace
        try:
            target.relative_to(workspace)
        except ValueError:
            logger.warning(f"[WRITE_FILE] Path violation: target={target} outside workspace={workspace}")
            return ToolResult(success=False, output="", error=f"Path '{raw_path}' resolves/escapes to '{target}', which is outside workspace '{workspace}'")

        try:
            if create_dirs:
                target.parent.mkdir(parents=True, exist_ok=True)
            
            logger.debug(f"[WRITE_FILE] Writing {len(content)} bytes to {target}")
            target.write_text(content, encoding='utf-8')

            # Explicitly report the action taken
            result_msg = f"✅ Wrote {len(content)} bytes to {target.relative_to(workspace)}"
            if str(clean_path) != str(raw_path):
                result_msg += f" (Note: Path sanitized from '{raw_path}' to ensure sandbox compliance)"

            logger.info(f"[WRITE_FILE] Success: {target.relative_to(workspace)}")
            return ToolResult(
                success=True,
                output=result_msg,
                citations=[str(target.relative_to(workspace))]
            )
        except Exception as e:
            logger.error(f"[WRITE_FILE] Write failed: {e}")
            return ToolResult(success=False, output="", error=f"Write failed: {e}")


class ReadFileTool(BaseTool):
    """Read file contents"""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="READ_FILE",
            category="filesystem",
            description="Read file contents",
            parameters=[
                ToolParameter(
                    name="path",
                    type="path",
                    required=True,
                    description="File path relative to workspace"
                ),
                ToolParameter(
                    name="start_line",
                    type="int",
                    required=False,
                    default=1,
                    description="First line to read"
                ),
                ToolParameter(
                    name="end_line",
                    type="int",
                    required=False,
                    default=None,
                    description="Last line to read (None = all)"
                )
            ],
            permissions=[ToolPermission.READ]
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        raw_path = params.get("path", "")
        start_line = params.get("start_line", 1)
        end_line = params.get("end_line")

        # Determine base directory: always use workspace (no cwd until CD is implemented)
        workspace = Path(context.workspace_path)
        
        # P1.3: Path Auto-Correction (same sanitization as WRITE_FILE)
        clean_path = raw_path

        # If the path is already an absolute path inside the workspace, just make it relative
        try:
            resolved = Path(raw_path).resolve()
            rel = resolved.relative_to(workspace.resolve())
            clean_path = str(rel)
        except (ValueError, OSError):
            # Not inside workspace — apply standard sanitization
            # Strip common hallucinated prefixes
            bad_prefixes = [
                '/home/user/workspace/', '/home/user/', '/tmp/', 'C:\\Users\\User\\',
                'current/directory/', '/current/directory/'
            ]
            
            for prefix in bad_prefixes:
                if clean_path.startswith(prefix) or clean_path.lower().startswith(prefix.lower()):
                    clean_path = clean_path[len(prefix):]
            
            # Force relative to avoid pathlib replacement behavior on Windows (C:/...)
            clean_path = clean_path.lstrip(r'\/')
            
            # Strip drive letters if present
            if ":" in clean_path:
                clean_path = clean_path.split(":")[-1].lstrip(r'\/')
        
        target = (workspace / clean_path).resolve()

        if str(clean_path) != str(raw_path):
            logger.info(f"[READ_FILE] Path sanitized: '{raw_path}' -> '{clean_path}'")

        # Security check - STRICT (same as WRITE_FILE)
        try:
            target.relative_to(workspace)
        except ValueError:
            logger.warning(f"[READ_FILE] Path violation: target={target} outside workspace={workspace}")
            return ToolResult(
                success=False, 
                output="", 
                error=f"Path outside workspace. Use LS to see available files in the workspace."
            )
        
        if not target.exists():
            return ToolResult(success=False, output="", error=f"File not found: {raw_path}")

        if not target.is_file():
            return ToolResult(success=False, output="", error=f"Not a file (use LS to list directory): {raw_path}")

        try:
            lines = []
            if read_file_content:
                try:
                    # Use CRS Lib smart reader (PDFs, Zips, Size Limits)
                    text = read_file_content(target)
                    lines = text.splitlines(keepends=False)
                except Exception as crs_e:
                    logger.warning(f"CRS Lib read failed for {target}: {crs_e}, falling back to plain text")
                    # Fallback if CRS fails (e.g. some obscure format)
                    with open(target, 'r', encoding='utf-8', errors='replace') as f:
                        lines = [line.rstrip() for line in f.readlines()]
            else:
                # Standard reader
                with open(target, 'r', encoding='utf-8', errors='replace') as f:
                    lines = [line.rstrip() for line in f.readlines()]

            total_lines = len(lines)
            start_line = max(1, min(start_line, total_lines))
            if end_line is None:
                end_line = total_lines
            else:
                end_line = max(start_line, min(end_line, total_lines))

            output_lines = [f"📄 File: {clean_path}"]
            if read_file_content:
                output_lines.append(f"⚙️ Reader: CRS Lib Smart Read")
            
            output_lines.append(f"📍 Lines {start_line}-{end_line} of {total_lines}\n")

            for i in range(start_line - 1, end_line):
                line_num = i + 1
                if i < len(lines):
                    output_lines.append(f"{line_num:4d} | {lines[i]}")

            if end_line < total_lines:
                output_lines.append(f"\n... {total_lines - end_line} more lines")

            return ToolResult(
                success=True,
                output="\n".join(output_lines),
                citations=[f"{clean_path}:{start_line}-{end_line}"]
            )

        except Exception as e:
            return ToolResult(success=False, output="", error=f"Read failed: {e}")



class AppendFileTool(BaseTool):
    """Append content to file"""

    def get_metadata(self) -> ToolMetadata:
        return ToolMetadata(
            name="APPEND_FILE",
            category="filesystem",
            description="Append content to existing file",
            parameters=[
                ToolParameter(name="path", type="path", required=True),
                ToolParameter(name="content", type="string", required=True)
            ],
            permissions=[ToolPermission.WRITE]
        )

    def execute(self, params: Dict[str, Any], context: ToolExecutionContext) -> ToolResult:
        path = params.get("path", "")
        content = params.get("content", "")

        workspace = Path(context.workspace_path)
        
        # Determine base directory: use CWD if set, otherwise workspace
        cwd = context.metadata.get("cwd", str(workspace))
        
        target = (Path(cwd) / path).resolve()

        try:
            target.relative_to(workspace)
        except ValueError:
            return ToolResult(success=False, output="", error="Path outside workspace")

        if not target.exists():
            return ToolResult(success=False, output="", error="File does not exist")

        try:
            with target.open('a', encoding='utf-8') as f:
                f.write(content)

            return ToolResult(
                success=True,
                output=f"✅ Appended {len(content)} bytes to {target.relative_to(workspace)}"
            )
        except Exception as e:
            return ToolResult(success=False, output="", error=f"Append failed: {e}")


def get_all_tools() -> List[type]:
    """Return all filesystem tools"""
    return [
        WriteFileTool,
        ReadFileTool,
        AppendFileTool
    ]
