#!/usr/bin/env python3
"""
Multi-keyword search tool
Supports keyword array matching, sorted by match count
"""

import argparse
import json
import os
import re
import sys
from typing import Any, Dict, List, Optional, Union


def parse_patterns_with_weights(patterns: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """Parse a list of search patterns, supporting weight format."""
    parsed_patterns = []

    for item in patterns:
        if not isinstance(item, dict):
            raise ValueError(f"Error: Search pattern must be in dictionary format with 'pattern' and 'weight' fields. Invalid item: {item}")

        pattern = item.get('pattern')
        weight = item.get('weight')

        if pattern is None:
            raise ValueError(f"Error: Missing 'pattern' field. Invalid item: {item}")

        if weight is None:
            raise ValueError(f"Error: Missing 'weight' field. Invalid item: {item}")

        # Ensure weight is a numeric type
        try:
            weight = float(weight)
            if weight <= 0:
                raise ValueError(f"Error: Weight must be a positive number. Invalid weight: {weight}")
        except (ValueError, TypeError):
            raise ValueError(f"Error: Weight must be a valid number. Invalid weight: {weight}")

        parsed_patterns.append({
            'pattern': pattern,
            'weight': weight
        })

    return parsed_patterns


def compile_pattern(pattern: str) -> Union[re.Pattern, str]:
    """Compile a pattern; return a Pattern object for regex, or the string otherwise."""
    if pattern.startswith('/') and pattern.endswith('/'):
        # Regex pattern
        regex_pattern = pattern[1:-1]
        try:
            return re.compile(regex_pattern)
        except re.error:
            print(f"Warning: Invalid regex '{pattern}', skipping...")
            return None
    else:
        # Plain keyword pattern
        return pattern


def search_patterns_in_file(file_path: str, patterns: List[Dict[str, Any]],
                          case_sensitive: bool) -> List[Dict[str, Any]]:
    """Search a single file for search patterns (keywords and regex), with weight scoring."""
    results = []

    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
    except Exception as e:
        print(f"Error reading file {file_path}: {e}")
        return results

    # Preprocess all patterns, including weight information
    processed_patterns = []
    for pattern_info in patterns:
        compiled = pattern_info['compiled_pattern']
        if compiled is not None:  # Skip invalid regex
            processed_patterns.append({
                'original': pattern_info['pattern'],
                'pattern': compiled,
                'is_regex': isinstance(compiled, re.Pattern),
                'weight': pattern_info['weight']
            })

    for line_number, line in enumerate(lines, 1):
        line_content = line.rstrip('\n\r')
        search_line = line_content if case_sensitive else line_content.lower()

        # Count matched patterns and calculate weight score
        matched_patterns = []
        weight_score = 0.0

        for pattern_info in processed_patterns:
            pattern = pattern_info['pattern']
            is_regex = pattern_info['is_regex']
            weight = pattern_info['weight']

            match_found = False
            match_details = None
            match_count_in_line = 0

            if is_regex:
                # Regex matching
                if case_sensitive:
                    matches = list(pattern.finditer(line_content))
                else:
                    # For case-insensitive regex, recompile
                    if isinstance(pattern, re.Pattern):
                        flags = pattern.flags | re.IGNORECASE
                        case_insensitive_pattern = re.compile(pattern.pattern, flags)
                        matches = list(case_insensitive_pattern.finditer(line_content))
                    else:
                        search_pattern = pattern.lower() if isinstance(pattern, str) else pattern
                        matches = list(re.finditer(search_pattern, search_line))

                if matches:
                    match_found = True
                    match_details = matches[0].group(0)
                    match_count_in_line = 1
            else:
                # Plain string matching
                search_keyword = pattern if case_sensitive else pattern.lower()
                if search_keyword in search_line:
                    match_found = True
                    match_details = pattern
                    match_count_in_line = 1

            if match_found:
                pattern_weight_score = weight * match_count_in_line
                weight_score += pattern_weight_score

                matched_patterns.append({
                    'original': pattern_info['original'],
                    'type': 'regex' if is_regex else 'keyword',
                    'match': match_details,
                    'weight': weight,
                    'match_count': match_count_in_line,
                    'weight_score': pattern_weight_score
                })

        if weight_score > 0:
            results.append({
                'line_number': line_number,
                'content': line_content,
                'match_count': len(matched_patterns),
                'weight_score': weight_score,
                'matched_patterns': matched_patterns,
                'file_path': file_path
            })

    return results


def search_count(patterns: List[Dict[str, Any]], file_paths: List[str],
               case_sensitive: bool = False) -> str:
    """Evaluate multi-pattern match counts."""
    if not patterns:
        return "Error: Search pattern list cannot be empty"

    try:
        parsed_patterns = parse_patterns_with_weights(patterns)
    except ValueError as e:
        return str(e)

    if not parsed_patterns:
        return "Error: No valid search patterns"

    if not file_paths:
        return "Error: File path list cannot be empty"

    # Preprocess and validate regex patterns in search patterns
    valid_patterns = []
    regex_errors = []

    for pattern_info in parsed_patterns:
        pattern = pattern_info['pattern']
        compiled = compile_pattern(pattern)
        if compiled is None:
            regex_errors.append(pattern)
        else:
            valid_patterns.append({
                'pattern': pattern,
                'weight': pattern_info['weight'],
                'compiled_pattern': compiled
            })

    if regex_errors:
        print(f"Warning: Invalid regex patterns: {', '.join(regex_errors)}")

    # Validate file paths
    valid_paths = [fp for fp in file_paths if os.path.exists(fp)]

    if not valid_paths:
        return "Error: No valid files found"

    # Collect all match results
    all_results = []

    for file_path in valid_paths:
        try:
            results = search_patterns_in_file(file_path, valid_patterns, case_sensitive)
            all_results.extend(results)
        except Exception as e:
            continue

    # Calculate statistics
    total_lines_searched = 0
    total_weight_score = 0.0
    pattern_match_stats = {}
    file_match_stats = {}

    for pattern_info in valid_patterns:
        pattern_key = pattern_info['pattern']
        pattern_match_stats[pattern_key] = {
            'match_count': 0,
            'weight_score': 0.0,
            'lines_matched': set()
        }

    for file_path in valid_paths:
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()
                total_lines_searched += len(lines)
        except Exception:
            continue

    for result in all_results:
        total_weight_score += result.get('weight_score', 0)

        file_path = result['file_path']
        if file_path not in file_match_stats:
            file_match_stats[file_path] = {
                'match_count': 0,
                'weight_score': 0.0,
                'lines_matched': set()
            }

        file_match_stats[file_path]['match_count'] += 1
        file_match_stats[file_path]['weight_score'] += result.get('weight_score', 0)
        file_match_stats[file_path]['lines_matched'].add(result['line_number'])

        for pattern in result['matched_patterns']:
            original_pattern = pattern['original']
            if original_pattern in pattern_match_stats:
                pattern_match_stats[original_pattern]['match_count'] += pattern['match_count']
                pattern_match_stats[original_pattern]['weight_score'] += pattern['weight_score']
                pattern_match_stats[original_pattern]['lines_matched'].add(result['line_number'])

    # Format statistics output
    formatted_lines = []
    formatted_lines.append("=== Matching Statistics Evaluation ===")
    formatted_lines.append(f"Files searched: {len(valid_paths)}")
    formatted_lines.append(f"Total lines searched: {total_lines_searched}")
    formatted_lines.append(f"Total matched lines: {len(all_results)}")
    formatted_lines.append(f"Total weight score: {total_weight_score:.2f}")
    if total_lines_searched > 0:
        formatted_lines.append(f"Match rate: {(len(all_results)/total_lines_searched*100):.2f}%")
    formatted_lines.append("")

    formatted_lines.append("=== Statistics by File ===")
    for file_path, stats in sorted(file_match_stats.items(), key=lambda x: x[1]['weight_score'], reverse=True):
        file_name = os.path.basename(file_path)
        formatted_lines.append(f"File: {file_name}")
        formatted_lines.append(f"  Matched lines: {len(stats['lines_matched'])}")
        formatted_lines.append(f"  Weight score: {stats['weight_score']:.2f}")
        formatted_lines.append("")

    formatted_lines.append("=== Statistics by Pattern ===")
    for pattern, stats in sorted(pattern_match_stats.items(), key=lambda x: x[1]['weight_score'], reverse=True):
        formatted_lines.append(f"Pattern: {pattern}")
        formatted_lines.append(f"  Match count: {stats['match_count']}")
        formatted_lines.append(f"  Matched lines: {len(stats['lines_matched'])}")
        formatted_lines.append(f"  Weight score: {stats['weight_score']:.2f}")
        formatted_lines.append("")

    return "\n".join(formatted_lines)


def search(patterns: List[Dict[str, Any]], file_paths: List[str],
          limit: int = 10, case_sensitive: bool = False) -> str:
    """Execute multi-pattern search."""
    if not patterns:
        return "Error: Search pattern list cannot be empty"

    try:
        parsed_patterns = parse_patterns_with_weights(patterns)
    except ValueError as e:
        return str(e)

    if not parsed_patterns:
        return "Error: No valid search patterns"

    if not file_paths:
        return "Error: File path list cannot be empty"

    # Preprocess and validate regex patterns in search patterns
    valid_patterns = []
    regex_errors = []

    for pattern_info in parsed_patterns:
        pattern = pattern_info['pattern']
        compiled = compile_pattern(pattern)
        if compiled is None:
            regex_errors.append(pattern)
        else:
            valid_patterns.append({
                'pattern': pattern,
                'weight': pattern_info['weight'],
                'compiled_pattern': compiled
            })

    if regex_errors:
        print(f"Warning: Invalid regex patterns: {', '.join(regex_errors)}")

    # Validate file paths
    valid_paths = [fp for fp in file_paths if os.path.exists(fp)]

    if not valid_paths:
        return "Error: No valid files found"

    # Collect all match results
    all_results = []

    for file_path in valid_paths:
        try:
            results = search_patterns_in_file(file_path, valid_patterns, case_sensitive)
            all_results.extend(results)
        except Exception as e:
            continue

    # Sort by weight score
    all_results.sort(key=lambda x: (x.get('weight_score', 0), x['match_count']), reverse=True)

    # Limit result count
    limited_results = all_results[:limit]

    if not limited_results:
        return "No matching results found"

    # Format output
    formatted_lines = []

    total_matches = len(all_results)
    showing_count = len(limited_results)
    summary_line = f"Found {total_matches} matches, showing top {showing_count} results:"
    formatted_lines.append(summary_line)

    for result in limited_results:
        weight_score = result.get('weight_score', 0)
        line_prefix = f"{result['line_number']}:weight({weight_score:.2f}):"

        # Build match details
        match_details = []
        for pattern in result['matched_patterns']:
            if pattern['type'] == 'regex':
                match_details.append(f"[regex:{pattern['original']}={pattern['match']}]")
            else:
                match_details.append(f"[keyword:{pattern['match']}]")

        match_info = " ".join(match_details) if match_details else ""
        formatted_line = f"{line_prefix}{match_info}:{result['content']}" if match_info else f"{line_prefix}{result['content']}"
        formatted_lines.append(formatted_line)

    return "\n".join(formatted_lines)


def regex_grep(patterns: Union[str, List[str]], file_paths: List[str], context_lines: int = 0,
              case_sensitive: bool = False, limit: int = 50) -> str:
    """Search file contents using regular expressions."""
    if isinstance(patterns, str):
        patterns = [patterns]

    if not patterns or not any(p.strip() for p in patterns):
        return "Error: Patterns cannot be empty"

    patterns = [p.strip() for p in patterns if p.strip()]

    if not file_paths:
        return "Error: File path list cannot be empty"

    # Compile regular expressions
    compiled_patterns = []
    for pattern in patterns:
        try:
            flags = 0 if case_sensitive else re.IGNORECASE
            compiled_pattern = re.compile(pattern, flags)
            compiled_patterns.append((pattern, compiled_pattern))
        except re.error as e:
            print(f"Warning: Invalid regex '{pattern}': {str(e)}, skipping...")
            continue

    if not compiled_patterns:
        return "Error: No valid regular expressions found"

    # Validate file paths
    valid_paths = [fp for fp in file_paths if os.path.exists(fp)]

    if not valid_paths:
        return "Error: No valid files found"

    # Collect all match results
    all_results = []

    for file_path in valid_paths:
        try:
            for pattern, compiled_pattern in compiled_patterns:
                results = regex_search_in_file(file_path, compiled_pattern, context_lines, case_sensitive, pattern)
                all_results.extend(results)
        except Exception as e:
            continue

    # Sort by file path and line number
    all_results.sort(key=lambda x: (x['file_path'], x['match_line_number']))

    # Limit result count
    limited_results = all_results[:limit]

    if not limited_results:
        return "No matches found"

    # Format output
    formatted_lines = []

    total_matches = len(all_results)
    showing_count = len(limited_results)
    summary_line = f"Found {total_matches} matches for {len(compiled_patterns)} patterns, showing top {showing_count} results:"
    formatted_lines.append(summary_line)

    # Group results by file
    current_file = None
    for result in limited_results:
        file_path = result['file_path']
        if file_path != current_file:
            current_file = file_path
            file_name = os.path.basename(file_path)
            formatted_lines.append(f"\n--- File: {file_name} ---")

        match_line = result['match_line_number']
        match_text = result['match_text']
        matched_content = result['matched_content']
        pattern = result.get('pattern', 'unknown')

        formatted_lines.append(f"{match_line}[pattern: {pattern}]:{matched_content}")

        # Show context lines
        if 'context_before' in result:
            for context_line in result['context_before']:
                formatted_lines.append(f"{context_line['line_number']}:{context_line['content']}")

        if 'context_after' in result:
            for context_line in result['context_after']:
                formatted_lines.append(f"{context_line['line_number']}:{context_line['content']}")

    return "\n".join(formatted_lines)


def regex_search_in_file(file_path: str, pattern: re.Pattern,
                        context_lines: int, case_sensitive: bool, pattern_str: str = None) -> List[Dict[str, Any]]:
    """Search for a regex in a single file, with context support."""
    results = []

    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
    except Exception as e:
        return results

    for line_number, line in enumerate(lines, 1):
        line_content = line.rstrip('\n\r')

        matches = list(pattern.finditer(line_content))
        if matches:
            context_before = []
            context_after = []

            if context_lines > 0:
                start_line = max(0, line_number - 1 - context_lines)
                for i in range(start_line, line_number - 1):
                    if i < len(lines):
                        context_before.append({
                            'line_number': i + 1,
                            'content': lines[i].rstrip('\n\r')
                        })

                end_line = min(len(lines), line_number + context_lines)
                for i in range(line_number, end_line):
                    if i < len(lines):
                        context_after.append({
                            'line_number': i + 1,
                            'content': lines[i].rstrip('\n\r')
                        })

            for match in matches:
                result = {
                    'file_path': file_path,
                    'match_line_number': line_number,
                    'match_text': line_content,
                    'matched_content': match.group(0),
                    'pattern': pattern_str or 'unknown',
                    'start_pos': match.start(),
                    'end_pos': match.end()
                }

                if context_before:
                    result['context_before'] = context_before

                if context_after:
                    result['context_after'] = context_after

                results.append(result)

    return results


def regex_grep_count(patterns: Union[str, List[str]], file_paths: List[str],
                    case_sensitive: bool = False) -> str:
    """Count matches using regular expressions."""
    if isinstance(patterns, str):
        patterns = [patterns]

    if not patterns or not any(p.strip() for p in patterns):
        return "Error: Patterns cannot be empty"

    patterns = [p.strip() for p in patterns if p.strip()]

    if not file_paths:
        return "Error: File path list cannot be empty"

    # Compile regular expressions
    compiled_patterns = []
    for pattern in patterns:
        try:
            flags = 0 if case_sensitive else re.IGNORECASE
            compiled_pattern = re.compile(pattern, flags)
            compiled_patterns.append((pattern, compiled_pattern))
        except re.error as e:
            print(f"Warning: Invalid regex '{pattern}': {str(e)}, skipping...")
            continue

    if not compiled_patterns:
        return "Error: No valid regular expressions found"

    # Validate file paths
    valid_paths = [fp for fp in file_paths if os.path.exists(fp)]

    if not valid_paths:
        return "Error: No valid files found"

    # Count match results
    total_matches = 0
    total_lines_with_matches = 0
    file_stats = {}
    pattern_stats = {}

    for pattern, _ in compiled_patterns:
        pattern_stats[pattern] = {
            'matches': 0,
            'lines_with_matches': 0
        }

    for file_path in valid_paths:
        file_name = os.path.basename(file_path)
        file_matches = 0
        file_lines_with_matches = 0

        try:
            for pattern, compiled_pattern in compiled_patterns:
                matches, lines_with_matches = regex_count_in_file(file_path, compiled_pattern, case_sensitive)
                total_matches += matches
                total_lines_with_matches += lines_with_matches
                file_matches += matches
                file_lines_with_matches = max(file_lines_with_matches, lines_with_matches)

                pattern_stats[pattern]['matches'] += matches
                pattern_stats[pattern]['lines_with_matches'] += lines_with_matches

            file_stats[file_name] = {
                'matches': file_matches,
                'lines_with_matches': file_lines_with_matches
            }
        except Exception as e:
            continue

    # Format output
    formatted_lines = []
    formatted_lines.append("=== Regex Match Statistics ===")
    formatted_lines.append(f"Patterns: {', '.join([p for p, _ in compiled_patterns])}")
    formatted_lines.append(f"Files searched: {len(valid_paths)}")
    formatted_lines.append(f"Total matches: {total_matches}")
    formatted_lines.append(f"Total lines with matches: {total_lines_with_matches}")
    formatted_lines.append("")

    formatted_lines.append("=== Statistics by Pattern ===")
    for pattern, stats in sorted(pattern_stats.items()):
        formatted_lines.append(f"Pattern: {pattern}")
        formatted_lines.append(f"  Matches: {stats['matches']}")
        formatted_lines.append(f"  Lines with matches: {stats['lines_with_matches']}")
        formatted_lines.append("")

    formatted_lines.append("=== Statistics by File ===")
    for file_name, stats in sorted(file_stats.items()):
        formatted_lines.append(f"File: {file_name}")
        formatted_lines.append(f"  Matches: {stats['matches']}")
        formatted_lines.append(f"  Lines with matches: {stats['lines_with_matches']}")
        formatted_lines.append("")

    return "\n".join(formatted_lines)


def regex_count_in_file(file_path: str, pattern: re.Pattern,
                       case_sensitive: bool) -> tuple[int, int]:
    """Count matches in a single file."""
    total_matches = 0
    lines_with_matches = 0

    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
    except Exception as e:
        return total_matches, lines_with_matches

    for line_number, line in enumerate(lines, 1):
        line_content = line.rstrip('\n\r')

        matches = list(pattern.finditer(line_content))
        if matches:
            total_matches += len(matches)
            lines_with_matches += 1

    return total_matches, lines_with_matches


def main():
    parser = argparse.ArgumentParser(description='Multi-keyword search with pattern matching and weight scoring')
    subparsers = parser.add_subparsers(dest='command', help='Available commands')

    # search command
    search_parser = subparsers.add_parser('search', help='Execute multi-keyword search')
    search_parser.add_argument('--patterns', required=True, help='JSON array of patterns with weights')
    search_parser.add_argument('--file-paths', required=True, nargs='+', help='Files to search')
    search_parser.add_argument('--limit', type=int, default=10, help='Max results')
    search_parser.add_argument('--case-sensitive', action='store_true', help='Case sensitive search')

    # search_count command
    count_parser = subparsers.add_parser('search_count', help='Count matching results')
    count_parser.add_argument('--patterns', required=True, help='JSON array of patterns with weights')
    count_parser.add_argument('--file-paths', required=True, nargs='+', help='Files to search')
    count_parser.add_argument('--case-sensitive', action='store_true', help='Case sensitive search')

    # regex_grep command
    grep_parser = subparsers.add_parser('regex_grep', help='Regex search with context')
    grep_parser.add_argument('--patterns', nargs='+', help='Regex patterns')
    grep_parser.add_argument('--file-paths', nargs='+', required=True, help='Files to search')
    grep_parser.add_argument('--context-lines', type=int, default=0, help='Context lines')
    grep_parser.add_argument('--case-sensitive', action='store_true', help='Case sensitive search')
    grep_parser.add_argument('--limit', type=int, default=50, help='Max results')

    # regex_grep_count command
    grep_count_parser = subparsers.add_parser('regex_grep_count', help='Count regex matches')
    grep_count_parser.add_argument('--patterns', nargs='+', help='Regex patterns')
    grep_count_parser.add_argument('--file-paths', nargs='+', required=True, help='Files to search')
    grep_count_parser.add_argument('--case-sensitive', action='store_true', help='Case sensitive search')

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(1)

    try:
        if args.command == 'search':
            patterns = json.loads(args.patterns)
            result = search(patterns, args.file_paths, args.limit, args.case_sensitive)
            print(result)

        elif args.command == 'search_count':
            patterns = json.loads(args.patterns)
            result = search_count(patterns, args.file_paths, args.case_sensitive)
            print(result)

        elif args.command == 'regex_grep':
            result = regex_grep(args.patterns, args.file_paths, args.context_lines, args.case_sensitive, args.limit)
            print(result)

        elif args.command == 'regex_grep_count':
            result = regex_grep_count(args.patterns, args.file_paths, args.case_sensitive)
            print(result)

    except json.JSONDecodeError as e:
        print(f"Error parsing patterns JSON: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()
