#!/usr/bin/env python3
"""Diff preprocessing (prototype).

gar에서는 원본 소스/원본 diff를 훼손하지 않고,
필요 시 "분석에서 제외할 섹션인지"만 판단하는 최소 로직만 유지한다.
"""

import logging
import re

from performance.constants.file_patterns import FilePatterns

logger = logging.getLogger(__name__)


def _extract_filename_from_section(section: str) -> str | None:
    """Extract filename from diff section header."""
    if not section or not isinstance(section, str):
        return None
    
    try:
        file_match = re.search(r"diff --git a/(.*) b/", section)
        if not file_match:
            return None
        filename = file_match.group(1)
        
        # Validate filename
        if not filename or filename.strip() == "":
            return None
        
        return filename
    except (re.error, IndexError) as e:
        logger.debug(f"Failed to extract filename from section: {e}")
        return None


def split_diff_into_sections(diff: str) -> list[str]:
    """기본 git diff를 파일 단위 섹션으로 분리."""
    if not diff:
        return []
    
    try:
        parts = re.split(r"(diff --git )", diff)
    except re.error as e:
        logger.error(f"Regex error in split_diff_into_sections: {e}")
        return []
    
    if parts and parts[0] == "":
        parts.pop(0)
    
    sections: list[str] = []
    i = 0
    while i < len(parts):
        if parts[i] == "diff --git " and i + 1 < len(parts):
            sections.append(parts[i] + parts[i + 1])
            i += 2
        else:
            sections.append(parts[i])
            i += 1
    
    return [s for s in sections if s.strip()]


def get_filtered_files_from_diff(diff: str) -> list[str]:
    """diff에서 필터를 통과한 파일 경로 목록을 반환."""
    if not diff or not isinstance(diff, str):
        logger.warning(f"Invalid diff input: type={type(diff)}, empty={not diff}")
        return []
    
    sections = split_diff_into_sections(diff)
    if not sections:
        logger.debug("No sections found in diff")
        return []
    
    result: list[str] = []
    for section in sections:
        try:
            if should_filter_section(section):
                continue
            fname = _extract_filename_from_section(section)
            if fname:
                result.append(fname)
        except Exception as e:
            logger.warning(f"Error processing diff section: {e}")
            continue
    
    return list(dict.fromkeys(result))  # dedup preserving order


def should_filter_section(section: str) -> bool:
    """Determine whether a diff section should be filtered out (excluded).

    NOTE: This only decides inclusion/exclusion. It does NOT mutate any files on disk.
    """
    if not section or not isinstance(section, str):
        logger.warning(f"Invalid section input: type={type(section)}")
        return True
    
    try:
        # Binary files
        if any(re.search(pattern, section) for pattern in FilePatterns.BINARY):
            filename = _extract_filename_from_section(section)
            if filename:
                logger.info(f"Filtered out binary file: {filename}")
            return True
    except re.error as e:
        logger.error(f"Regex error checking binary patterns: {e}")
        return True

    filename = _extract_filename_from_section(section)
    if not filename:
        logger.debug("Could not extract filename from section")
        return False

    try:
        # Minified files
        if any(filename.endswith(ext) for ext in FilePatterns.MINIFIED_EXTENSIONS):
            logger.info(f"Filtered out minified file by extension: {filename}")
            return True

        # Build directories
        if any(directory in filename for directory in FilePatterns.BUILD_DIRECTORIES):
            logger.info(f"Filtered out file in build directory: {filename}")
            return True

        # Config files (.env, .gitignore, etc.)
        if any(re.search(pattern, filename) for pattern in FilePatterns.CONFIG_FILES):
            logger.info(f"Filtered out config file: {filename}")
            return True

        # Documentation files (*.md, README, etc.)
        if any(re.search(pattern, filename) for pattern in FilePatterns.DOCUMENTATION_FILES):
            logger.info(f"Filtered out documentation file: {filename}")
            return True

        # Editor/IDE settings
        if any(re.search(pattern, filename) for pattern in FilePatterns.EDITOR_SETTINGS):
            logger.info(f"Filtered out editor settings: {filename}")
            return True

        # Lock files and generated files
        if is_lockfile_or_generated(filename):
            logger.info(f"Filtered out lockfile or generated file: {filename}")
            return True

        # Minified content
        if is_minified_content(section):
            logger.info(f"Filtered out likely minified file by content: {filename}")
            return True
    except Exception as e:
        logger.error(f"Error filtering section for {filename}: {e}")
        return True

    return False


def is_lockfile_or_generated(filename: str) -> bool:
    """Check if filename matches lockfile or generated file patterns."""
    if not filename or not isinstance(filename, str):
        return False
    
    lockfile_patterns = [
        r"package-lock\.json$",
        r"yarn\.lock$",
        r"Pipfile\.lock$",
        r"poetry\.lock$",
        r"Gemfile\.lock$",
        r"pnpm-lock\.yaml$",
        r"composer\.lock$",
        r"Cargo\.lock$",
        r"\.sum$",
    ]

    generated_patterns = [
        r"\.pb\.go$",
        r"\.g\.dart$",
        r"autogen\.",
        r"generated\.",
    ]

    try:
        return any(re.search(pattern, filename) for pattern in lockfile_patterns) or any(
            re.search(pattern, filename) for pattern in generated_patterns
        )
    except re.error as e:
        logger.error(f"Regex error in is_lockfile_or_generated for {filename}: {e}")
        return False


def is_minified_content(content: str) -> bool:
    """Heuristic minified-content detector (conservative)."""
    if not content:
        return False

    lines = content.split("\n")
    if not lines:
        return False

    if len(lines) < 10 and len(content) > 4000:
        return True

    if len(lines) == 1 and len(lines[0]) > 2000:
        return True

    long_lines_count = sum(1 for line in lines if len(line) > 1000)
    if long_lines_count > 0 and (long_lines_count / max(1, len(lines))) > 0.5:
        return True

    return False
