from __future__ import annotations

import hashlib
import os
from typing import List, Set

from bastion.models import PluginUnit

SKIP_DIRS: Set[str] = {
    ".git", "node_modules", "__pycache__", ".bastion",
    ".venv", "venv", "dist", "build", ".tox", ".mypy_cache",
    ".pytest_cache", "egg-info",
}

PLUGIN_DIRS: Set[str] = {
    "skills", "tools", "plugins", "extensions", "agents", "actions",
}

PLUGIN_FILE_SUFFIXES = ("_skill", "_tool", "_plugin", "_extension")

CODE_EXTENSIONS_PYTHON = {".py"}
CODE_EXTENSIONS_JS = {".js", ".ts", ".jsx", ".tsx"}
CODE_EXTENSIONS = CODE_EXTENSIONS_PYTHON | CODE_EXTENSIONS_JS


def _should_skip(dirname: str) -> bool:
    return dirname in SKIP_DIRS or dirname.startswith(".")


def _compute_hash(file_path: str) -> str:
    h = hashlib.sha256()
    try:
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(8192), b""):
                h.update(chunk)
    except (OSError, IOError):
        pass
    return h.hexdigest()


def _compute_dir_hash(dir_path: str) -> str:
    h = hashlib.sha256()
    for root, dirs, files in os.walk(dir_path):
        dirs[:] = [d for d in sorted(dirs) if not _should_skip(d)]
        for fname in sorted(files):
            ext = os.path.splitext(fname)[1].lower()
            if ext in CODE_EXTENSIONS:
                fpath = os.path.join(root, fname)
                try:
                    with open(fpath, "rb") as f:
                        for chunk in iter(lambda: f.read(8192), b""):
                            h.update(chunk)
                except (OSError, IOError):
                    continue
    return h.hexdigest()


def _detect_language_for_dir(dir_path: str) -> str:
    has_py = False
    has_js = False
    for root, dirs, files in os.walk(dir_path):
        dirs[:] = [d for d in dirs if not _should_skip(d)]
        for fname in files:
            ext = os.path.splitext(fname)[1].lower()
            if ext in CODE_EXTENSIONS_PYTHON:
                has_py = True
            elif ext in CODE_EXTENSIONS_JS:
                has_js = True
    if has_py and not has_js:
        return "python"
    if has_js and not has_py:
        return "javascript"
    if has_py:
        return "python"
    return "javascript"


def _detect_language_for_file(file_path: str) -> str:
    ext = os.path.splitext(file_path)[1].lower()
    if ext in CODE_EXTENSIONS_PYTHON:
        return "python"
    return "javascript"


def _is_plugin_file(filename: str) -> bool:
    name_lower = filename.lower()
    base, ext = os.path.splitext(name_lower)
    if ext not in CODE_EXTENSIONS:
        return False
    for suffix in PLUGIN_FILE_SUFFIXES:
        if base.endswith(suffix):
            return True
    return False


def _generate_plugin_id(path: str) -> str:
    path_hash = hashlib.sha256(path.encode()).hexdigest()[:12]
    return f"plugin-{path_hash}"


def discover_plugins(project_path: str, openclaw_mode: bool = False) -> List[PluginUnit]:
    project_path = os.path.abspath(project_path)
    plugins: List[PluginUnit] = []
    seen_paths: Set[str] = set()

    folder_plugin_dirs: List[str] = []
    file_plugins: List[str] = []

    for root, dirs, files in os.walk(project_path):
        dirs[:] = [d for d in sorted(dirs) if not _should_skip(d)]

        current_dir_name = os.path.basename(root)

        if current_dir_name.lower() in PLUGIN_DIRS:
            for subdir in sorted(dirs):
                subdir_path = os.path.join(root, subdir)
                if not _should_skip(subdir):
                    has_code = False
                    for sub_root, _, sub_files in os.walk(subdir_path):
                        for sf in sub_files:
                            if os.path.splitext(sf)[1].lower() in CODE_EXTENSIONS:
                                has_code = True
                                break
                        if has_code:
                            break
                    if has_code:
                        folder_plugin_dirs.append(subdir_path)

            for fname in sorted(files):
                ext = os.path.splitext(fname)[1].lower()
                if ext in CODE_EXTENSIONS:
                    file_plugins.append(os.path.join(root, fname))

        if openclaw_mode and current_dir_name.lower() == "skills":
            for fname in sorted(files):
                if "skill" in fname.lower():
                    fpath = os.path.join(root, fname)
                    ext = os.path.splitext(fname)[1].lower()
                    if ext in CODE_EXTENSIONS and fpath not in file_plugins:
                        file_plugins.append(fpath)

        for fname in sorted(files):
            if _is_plugin_file(fname):
                fpath = os.path.join(root, fname)
                if fpath not in file_plugins:
                    file_plugins.append(fpath)

    for dir_path in folder_plugin_dirs:
        abs_path = os.path.abspath(dir_path)
        if abs_path in seen_paths:
            continue
        seen_paths.add(abs_path)

        rel_path = os.path.relpath(abs_path, project_path)
        name = os.path.basename(abs_path)
        language = _detect_language_for_dir(abs_path)
        content_hash = _compute_dir_hash(abs_path)

        plugins.append(PluginUnit(
            id=_generate_plugin_id(rel_path),
            name=name,
            path=rel_path,
            type="folder",
            language=language,
            hash=content_hash,
        ))

    for fpath in file_plugins:
        abs_path = os.path.abspath(fpath)
        if abs_path in seen_paths:
            continue

        parent_abs = os.path.dirname(abs_path)
        parent_is_folder_plugin = any(
            os.path.abspath(d) == parent_abs or abs_path.startswith(os.path.abspath(d) + os.sep)
            for d in folder_plugin_dirs
        )
        if parent_is_folder_plugin:
            continue

        seen_paths.add(abs_path)

        rel_path = os.path.relpath(abs_path, project_path)
        name = os.path.splitext(os.path.basename(abs_path))[0]
        language = _detect_language_for_file(abs_path)
        content_hash = _compute_hash(abs_path)

        plugins.append(PluginUnit(
            id=_generate_plugin_id(rel_path),
            name=name,
            path=rel_path,
            type="file",
            language=language,
            hash=content_hash,
        ))

    return plugins
