import fnmatch
import re

from cecli.tools.utils.base_tool import BaseTool


class Tool(BaseTool):
    NORM_NAME = "viewfilesmatching"
    SCHEMA = {
        "type": "function",
        "function": {
            "name": "ViewFilesMatching",
            "description": "View files containing a specific pattern.",
            "parameters": {
                "type": "object",
                "properties": {
                    "pattern": {
                        "type": "string",
                        "description": "The pattern to search for in file contents.",
                    },
                    "file_pattern": {
                        "type": "string",
                        "description": (
                            "An optional glob pattern to filter which files are searched."
                        ),
                    },
                    "regex": {
                        "type": "boolean",
                        "description": (
                            "Whether the pattern is a regular expression. Defaults to False."
                        ),
                    },
                },
                "required": ["pattern"],
            },
        },
    }

    @classmethod
    def execute(cls, coder, pattern, file_pattern=None, regex=False):
        """
        Search for pattern (literal string or regex) in files and return matching files as text.

        Args:
            coder: The Coder instance.
            pattern (str): The pattern to search for.
                Treated as a literal string by default.
            file_pattern (str, optional): Glob pattern to filter which files are searched.
                Defaults to None (search all files).
            regex (bool, optional): If True, treat pattern as a regular expression.
                Defaults to False.

        This tool lets the LLM search for content within files, mimicking
        how a developer would use grep or regex search to find relevant code.
        """
        try:
            # Get list of files to search
            if file_pattern:
                # Use glob pattern to filter files
                all_files = coder.get_all_relative_files()
                files_to_search = []
                for file in all_files:
                    if fnmatch.fnmatch(file, file_pattern):
                        files_to_search.append(file)

                if not files_to_search:
                    return f"No files matching '{file_pattern}' to search for pattern '{pattern}'"
            else:
                # Search all files if no pattern provided
                files_to_search = coder.get_all_relative_files()

            # Search for pattern in files
            matches = {}
            num_matches = 0
            inspecific_search_flag = False

            for file in files_to_search:
                abs_path = coder.abs_root_path(file)

                if num_matches >= 25:
                    inspecific_search_flag = True

                try:
                    if coder.repo.ignored_file(abs_path):
                        continue

                    with open(abs_path, "r", encoding="utf-8") as f:
                        content = f.read()
                        match_count = 0
                        if regex:
                            try:
                                matches_found = re.findall(pattern, content)
                                match_count = len(matches_found)
                            except re.error as e:
                                # Handle invalid regex patterns gracefully
                                coder.io.tool_error(f"Invalid regex pattern '{pattern}': {e}")
                                # Skip this file for this search if regex is invalid
                                continue
                        else:
                            # Exact string matching
                            match_count = content.count(pattern)

                        if match_count > 0:
                            matches[file] = match_count
                            num_matches += 1
                except Exception:
                    # Skip files that can't be read (binary, etc.)
                    pass

            # Return formatted text instead of adding to context
            if matches:
                # Sort by number of matches (most matches first)
                sorted_matches = sorted(matches.items(), key=lambda x: x[1], reverse=True)
                match_list = [f"{file} ({count} matches)" for file, count in sorted_matches]

                if len(matches) > 10:
                    result = (
                        f"Found '{pattern}' in {len(matches)} files:"
                        f" {', '.join(match_list[:10])} and {len(matches) - 10} more"
                        "\nTry more specific search terms going forward"
                        if inspecific_search_flag
                        else ""
                    )
                    coder.io.tool_output(f"🔍 Found '{pattern}' in {len(matches)} files")
                else:
                    result = f"Found '{pattern}' in {len(matches)} files: {', '.join(match_list)}"
                    coder.io.tool_output(
                        f"🔍 Found '{pattern}' in:"
                        f" {', '.join(match_list[:5])}{' and more' if len(matches) > 5 else ''}"
                    )

                return result
            else:
                coder.io.tool_output(f"⚠️ Pattern '{pattern}' not found in any files")
                return "Pattern not found in any files"
        except Exception as e:
            coder.io.tool_error(f"Error in ViewFilesMatching: {str(e)}")
            return f"Error: {str(e)}"
