"""Shell execution tool."""

import asyncio
import os
import re
from pathlib import Path
from typing import Any

from nanobot.agent.tools.base import Tool


class ExecTool(Tool):
    """Tool to execute shell commands."""
    
    def __init__(
        self,
        timeout: int = 60,
        working_dir: str | None = None,
        deny_patterns: list[str] | None = None,
        allow_patterns: list[str] | None = None,
        restrict_to_workspace: bool = False,
    ):
        self.timeout = timeout
        self.working_dir = working_dir
        self.deny_patterns = deny_patterns or [
            r"\brm\s+-[rf]{1,2}\b",          # rm -r, rm -rf, rm -fr
            r"\bdel\s+/[fq]\b",              # del /f, del /q
            r"\brmdir\s+/s\b",               # rmdir /s
            r"\b(format|mkfs|diskpart)\b",   # disk operations
            r"\bdd\s+if=",                   # dd
            r">\s*/dev/sd",                  # write to disk
            r"\b(shutdown|reboot|poweroff)\b",  # system power
            r":\(\)\s*\{.*\};\s*:",          # fork bomb
        ]
        self.allow_patterns = allow_patterns or []
        self.restrict_to_workspace = restrict_to_workspace
    
    @property
    def name(self) -> str:
        return "exec"
    
    @property
    def description(self) -> str:
        return """Execute a shell command and return its output. Use with caution.

IMPORTANT: 
- For READING files (including PDFs, text files, etc.), ALWAYS use read_file FIRST. Do NOT use exec to read files.
- Only use exec for complex data processing AFTER you have already read the file content using read_file.
- For git commands (git commit, git push, git status, etc.), ALWAYS use exec tool, NOT write_file or edit_file.

For data analysis tasks (Excel, CSV, JSON files), use Python with pandas:
- Excel files: python3 -c "import pandas as pd; df = pd.read_excel('file.xlsx'); result = df['Column Name'].sum(); print(result)"
- CSV files: python3 -c "import pandas as pd; df = pd.read_csv('file.csv'); result = df['Column Name'].sum(); print(result)"
- NEVER use pandas/openpyxl as command-line tools (they don't exist)
- NEVER use non-existent tools like csvcalc, xlsxcalc, etc.
- For calculations: Use pandas operations like .sum(), .mean(), .max(), etc.
- For total inventory value: (df['Unit Price'] * df['Quantity']).sum()

For git operations:
- git commit: exec(command="git commit -m 'message'")
- git status: exec(command="git status")
- git push: exec(command="git push")
- NEVER use write_file or edit_file for git commands"""
    
    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "The shell command to execute. For data analysis, use: python3 -c \"import pandas as pd; df = pd.read_csv('file.csv'); print(df['Column'].sum())\""
                },
                "working_dir": {
                    "type": "string",
                    "description": "Optional working directory for the command"
                }
            },
            "required": ["command"]
        }
    
    async def execute(self, command: str, working_dir: str | None = None, **kwargs: Any) -> str:
        cwd = working_dir or self.working_dir or os.getcwd()
        
        # Sanitize Gitea API URLs: convert HTTPS to HTTP for 10.0.30.169:3000
        command = self._sanitize_gitea_urls(command)
        
        guard_error = self._guard_command(command, cwd)
        if guard_error:
            return guard_error
        
        # DEBUG: Log command details
        from loguru import logger
        logger.debug(f"ExecTool: command={command[:200]}, cwd={cwd}, working_dir={working_dir}")
        
        try:
            # Ensure environment variables are available (including from .env file)
            env = os.environ.copy()
            process = await asyncio.create_subprocess_shell(
                command,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
                env=env,
            )
            
            try:
                stdout, stderr = await asyncio.wait_for(
                    process.communicate(),
                    timeout=self.timeout
                )
            except asyncio.TimeoutError:
                process.kill()
                return f"Error: Command timed out after {self.timeout} seconds"
            
            output_parts = []
            
            if stdout:
                stdout_text = stdout.decode("utf-8", errors="replace")
                output_parts.append(stdout_text)
                logger.debug(f"ExecTool stdout: {stdout_text[:200]}")
            
            if stderr:
                stderr_text = stderr.decode("utf-8", errors="replace")
                if stderr_text.strip():
                    output_parts.append(f"STDERR:\n{stderr_text}")
                    logger.debug(f"ExecTool stderr: {stderr_text[:200]}")
            
            if process.returncode != 0:
                output_parts.append(f"\nExit code: {process.returncode}")
                logger.warning(f"ExecTool: Command failed with exit code {process.returncode}")
            
            result = "\n".join(output_parts) if output_parts else "(no output)"
            
            # DEBUG: For Excel operations, verify file was actually modified
            if "to_excel" in command or ".xlsx" in command:
                import re
                import time
                xlsx_matches = re.findall(r"['\"]([^'\"]*\.xlsx)['\"]", command)
                if xlsx_matches:
                    file_path = Path(xlsx_matches[0]).expanduser()
                    logger.debug(f"ExecTool: Checking Excel file {file_path}")
                    if file_path.exists():
                        file_mtime = file_path.stat().st_mtime
                        time_since_mod = time.time() - file_mtime
                        logger.debug(f"ExecTool: File mtime={file_mtime}, time_since_mod={time_since_mod}")
                        if time_since_mod < 5:
                            result += f"\n✅ Verified: File {file_path} was modified {time_since_mod:.2f}s ago"
                        else:
                            result += f"\n⚠️  WARNING: File {file_path} was NOT recently modified (last modified {time_since_mod:.2f}s ago). Command may not have saved changes."
                            logger.warning(f"ExecTool: Excel file {file_path} was not recently modified!")
                    else:
                        logger.warning(f"ExecTool: Excel file {file_path} does not exist!")
                        result += f"\n⚠️  WARNING: File {file_path} does not exist!"
            
            # Verify file operations for Excel files (common issue: pandas to_excel not saving)
            # Check if command mentions Excel file operations
            if "to_excel" in command or ".xlsx" in command:
                import re
                # Try to extract file path from command
                xlsx_matches = re.findall(r"['\"]([^'\"]*\.xlsx)['\"]", command)
                if xlsx_matches:
                    file_path = Path(xlsx_matches[0]).expanduser()
                    if file_path.exists():
                        # Check if file was recently modified (within last 5 seconds)
                        import time
                        file_mtime = file_path.stat().st_mtime
                        if time.time() - file_mtime < 5:
                            result += f"\n✅ Verified: File {file_path} was modified"
                        else:
                            result += f"\n⚠️  Warning: File {file_path} exists but wasn't recently modified. Command may not have saved changes."
            
            # Truncate very long output
            max_len = 10000
            if len(result) > max_len:
                result = result[:max_len] + f"\n... (truncated, {len(result) - max_len} more chars)"
            
            return result
            
        except Exception as e:
            return f"Error executing command: {str(e)}"

    def _guard_command(self, command: str, cwd: str) -> str | None:
        """Best-effort safety guard for potentially destructive commands."""
        cmd = command.strip()
        lower = cmd.lower()

        for pattern in self.deny_patterns:
            if re.search(pattern, lower):
                return "Error: Command blocked by safety guard (dangerous pattern detected)"

        if self.allow_patterns:
            if not any(re.search(p, lower) for p in self.allow_patterns):
                return "Error: Command blocked by safety guard (not in allowlist)"

        if self.restrict_to_workspace:
            if "..\\" in cmd or "../" in cmd:
                return "Error: Command blocked by safety guard (path traversal detected)"

            cwd_path = Path(cwd).resolve()

            win_paths = re.findall(r"[A-Za-z]:\\[^\\\"']+", cmd)
            # Only match absolute paths — avoid false positives on relative
            # paths like ".venv/bin/python" where "/bin/python" would be
            # incorrectly extracted by the old pattern.
            posix_paths = re.findall(r"(?:^|[\s|>])(/[^\s\"'>]+)", cmd)

            for raw in win_paths + posix_paths:
                try:
                    p = Path(raw.strip()).resolve()
                except Exception:
                    continue
                if p.is_absolute() and cwd_path not in p.parents and p != cwd_path:
                    return "Error: Command blocked by safety guard (path outside working dir)"

        return None
    
    def _sanitize_gitea_urls(self, command: str) -> str:
        """
        Sanitize Gitea API URLs in curl commands: convert HTTPS to HTTP.
        
        Gitea API at 10.0.30.169:3000 runs on HTTP, not HTTPS.
        This prevents SSL/TLS errors when the agent generates HTTPS URLs.
        """
        # Pattern to match https://10.0.30.169:3000/api/... in curl commands
        # This handles various curl formats:
        # - curl "https://10.0.30.169:3000/api/..."
        # - curl -X GET https://10.0.30.169:3000/api/...
        # - curl -H "..." "https://10.0.30.169:3000/api/..."
        # Matches URLs with or without quotes, and captures the full path
        pattern = r'https://10\.0\.30\.169:3000(/api/[^\s"\']*)'
        
        def replace_url(match):
            path = match.group(1)
            return f'http://10.0.30.169:3000{path}'
        
        sanitized = re.sub(pattern, replace_url, command)
        
        # Log if we made a change
        if sanitized != command:
            from loguru import logger
            logger.info(f"ExecTool: Sanitized Gitea API URL (HTTPS -> HTTP)")
            logger.debug(f"Original: {command[:200]}...")
            logger.debug(f"Sanitized: {sanitized[:200]}...")
        
        return sanitized
