"""Project discovery utilities for finding and suggesting similar projects."""

from __future__ import annotations

import logging
from functools import lru_cache
from pathlib import Path

logger = logging.getLogger(__name__)

# Project type identifiers
KOTLIN_PROJECT = "kotlin"
PYTHON_PROJECT = "python"


@lru_cache(maxsize=4)
def discover_kotlin_projects(workspace_root: Path) -> list[Path]:
    """
    Discover all Kotlin/Gradle projects in the workspace.

    Looks for build.gradle.kts or build.gradle files in standard locations:
    - microservices/*/
    - libraries/*/
    - libraries/*/*/  (nested like libraries/calq/calcique-lib)
    - components/*/

    Returns:
        List of project paths relative to workspace root.
    """
    projects: list[Path] = []
    search_dirs = ["microservices", "libraries", "components"]

    for search_dir in search_dirs:
        base_dir = workspace_root / search_dir
        if not base_dir.exists():
            continue

        # Direct children (e.g., microservices/calcique)
        for child in base_dir.iterdir():
            if child.is_dir() and _is_gradle_project(child):
                projects.append(child.relative_to(workspace_root))

            # Nested children (e.g., libraries/calq/calcique-lib)
            if child.is_dir():
                for nested in child.iterdir():
                    if nested.is_dir() and _is_gradle_project(nested):
                        projects.append(nested.relative_to(workspace_root))

    return sorted(projects, key=lambda p: str(p))


@lru_cache(maxsize=4)
def discover_python_projects(workspace_root: Path) -> list[Path]:
    """
    Discover all Python projects in the workspace.

    Looks for pyproject.toml or setup.py files in standard locations:
    - microservices/*/
    - libraries/*/
    - components/*/
    - packages/*/
    - top-level project directories

    Returns:
        List of project paths relative to workspace root.
    """
    projects: list[Path] = []
    search_dirs = ["microservices", "libraries", "components", "packages"]

    for search_dir in search_dirs:
        base_dir = workspace_root / search_dir
        if not base_dir.exists():
            continue

        # Direct children
        for child in base_dir.iterdir():
            if child.is_dir() and _is_python_project(child):
                projects.append(child.relative_to(workspace_root))

            # Nested children
            if child.is_dir():
                for nested in child.iterdir():
                    if nested.is_dir() and _is_python_project(nested):
                        projects.append(nested.relative_to(workspace_root))

    # Also check top-level directories (for standalone tools/packages at root).
    for child in workspace_root.iterdir():
        if not child.is_dir() or child.name.startswith("."):
            continue
        if child.name in search_dirs:
            continue
        if _is_python_project(child):
            projects.append(child.relative_to(workspace_root))

    return sorted(set(projects), key=lambda p: str(p))


def _is_gradle_project(path: Path) -> bool:
    """Check if a directory is a Gradle project."""
    return (path / "build.gradle.kts").exists() or (path / "build.gradle").exists()


def _is_python_project(path: Path) -> bool:
    """Check if a directory is a Python project."""
    return (path / "pyproject.toml").exists() or (path / "setup.py").exists()


def find_similar_projects(
    requested_path: Path,
    workspace_root: Path,
    project_type: str,
    max_suggestions: int = 5,
) -> list[Path]:
    """
    Find projects with similar names to the requested path.

    Uses simple substring matching and name similarity to suggest alternatives.

    Args:
        requested_path: The path that was requested but not found
        workspace_root: Workspace root directory
        project_type: Either KOTLIN_PROJECT or PYTHON_PROJECT
        max_suggestions: Maximum number of suggestions to return

    Returns:
        List of similar project paths (relative to workspace root)
    """
    if project_type == KOTLIN_PROJECT:
        all_projects = discover_kotlin_projects(workspace_root)
    elif project_type == PYTHON_PROJECT:
        all_projects = discover_python_projects(workspace_root)
    else:
        return []

    # Extract the project name from the requested path
    # Handle both absolute and relative paths
    if requested_path.is_absolute():
        try:
            requested_rel = requested_path.relative_to(workspace_root)
        except ValueError:
            requested_rel = requested_path
    else:
        requested_rel = requested_path

    requested_name = requested_rel.name.lower()
    requested_parts = set(requested_rel.parts)
    requested_str = str(requested_rel).lower()

    # Score each project by similarity
    scored_projects: list[tuple[float, Path]] = []

    for project in all_projects:
        project_name = project.name.lower()
        project_parts = set(project.parts)
        project_str = str(project).lower()

        score = 0.0

        # Exact name match (high score)
        if requested_name == project_name:
            score += 100.0

        # Name contains requested name or vice versa
        if requested_name in project_name:
            score += 50.0
        elif project_name in requested_name:
            score += 40.0

        # Partial name overlap (for compound names like "calcique-lib")
        if _has_common_substrings(requested_name, project_name, min_length=4):
            score += 30.0

        # Path component overlap
        common_parts = requested_parts & project_parts
        score += len(common_parts) * 10.0

        # String similarity for the full path
        if requested_str in project_str or project_str in requested_str:
            score += 20.0

        if score > 0:
            scored_projects.append((score, project))

    # Sort by score (descending) and return top suggestions
    scored_projects.sort(key=lambda x: (-x[0], str(x[1])))
    return [project for _, project in scored_projects[:max_suggestions]]


def _has_common_substrings(s1: str, s2: str, min_length: int = 4) -> bool:
    """Check if two strings share a common substring of at least min_length."""
    if len(s1) < min_length or len(s2) < min_length:
        return False

    # Check all substrings of s1 of length min_length or more
    for i in range(len(s1) - min_length + 1):
        for j in range(i + min_length, len(s1) + 1):
            substring = s1[i:j]
            if substring in s2:
                return True
    return False


def format_project_not_found_error(
    requested_path: Path,
    workspace_root: Path,
    project_type: str,
) -> str:
    """
    Format a helpful error message when a project is not found.

    Includes suggestions for similar projects if any are found.

    Args:
        requested_path: The path that was requested but not found
        workspace_root: Workspace root directory
        project_type: Either KOTLIN_PROJECT or PYTHON_PROJECT

    Returns:
        Formatted error message with suggestions
    """
    similar = find_similar_projects(requested_path, workspace_root, project_type)

    error_msg = f"❌ Project not found: {requested_path}"

    if similar:
        error_msg += "\n\n💡 Did you mean one of these?\n"
        for project in similar:
            error_msg += f"  → {project}\n"

    return error_msg
