import os
from pathlib import Path

import pathspec

from c2m_by_nery.core.config import ConfigManager

EXCLUDED_DIRS = frozenset({
    ".git", "__pycache__", ".venv", "venv", "node_modules", ".svn", ".hg",
    ".dev-toolbox",
    ".c2m",
})

DOCUMENT_EXTENSIONS = frozenset({
    ".pdf", ".docx", ".pptx", ".html", ".htm", ".md", ".csv",
    ".xlsx", ".asciidoc", ".adoc",
})

BINARY_EXTENSIONS = frozenset({
    ".pyc", ".pyo", ".so", ".dll", ".dylib", ".exe", ".bin",
    ".o", ".a", ".lib", ".obj", ".class", ".jar", ".war",
    ".zip", ".tar", ".gz", ".bz2", ".xz", ".7z", ".rar",
    ".png", ".jpg", ".jpeg", ".gif", ".ico", ".bmp",
    ".mp3", ".mp4", ".avi", ".mov", ".wav", ".webm",
    ".woff", ".woff2", ".ttf", ".eot", ".otf",
    ".db", ".sqlite", ".sqlite3",
})


def _is_binary_path(path: Path) -> bool:
    ext = path.suffix.lower()
    if ext in DOCUMENT_EXTENSIONS:
        return False
    return ext in BINARY_EXTENSIONS


def _is_binary_content(path: Path, chunk_size: int = 8192) -> bool:
    try:
        with open(path, "rb") as f:
            chunk = f.read(chunk_size)
        return b"\x00" in chunk
    except (OSError, PermissionError):
        return True


def _is_excluded_dir(name: str) -> bool:
    return name in EXCLUDED_DIRS


class Scanner:
    def __init__(self, root: Path, config: ConfigManager) -> None:
        self.root = Path(root).resolve()
        self.config = config

    def scan(self) -> list[Path]:
        all_files: list[Path] = []
        root_str = str(self.root)
        for dirpath, dirnames, filenames in os.walk(root_str):
            dirnames[:] = [d for d in dirnames if not _is_excluded_dir(d)]
            dir_path = Path(dirpath)
            rel_dir = dir_path.relative_to(self.root) if dirpath != root_str else Path(".")
            for name in filenames:
                filepath = dir_path / name
                rel_path = rel_dir / name
                if self._should_include(filepath, rel_path):
                    all_files.append(filepath)
        return all_files

    def _should_include(self, filepath: Path, rel_path: Path) -> bool:
        if _is_binary_path(filepath):
            return False
        ext = filepath.suffix.lower()
        if ext not in DOCUMENT_EXTENSIONS and _is_binary_content(filepath):
            return False
        if self._matches_extra_patterns(rel_path):
            return False
        if self._is_gitignored(filepath):
            return False
        return True

    def _matches_extra_patterns(self, rel_path: Path) -> bool:
        if not self.config.ignore_patterns:
            return False
        rel_str = str(rel_path).replace("\\", "/")
        spec = pathspec.PathSpec.from_lines("gitignore", self.config.ignore_patterns)
        return spec.match_file(rel_str)

    def _is_gitignored(self, filepath: Path) -> bool:
        try:
            import git

            repo = git.Repo(self.root, search_parent_directories=True)
            try:
                rel = filepath.relative_to(repo.working_tree_dir)
            except ValueError:
                return False
            rel_str = str(rel).replace("\\", "/")
            ignored = repo.ignored(rel_str)
            return rel_str in ignored or str(filepath) in ignored
        except Exception:
            return False
