from pathlib import Path

from .models import DiscoveryResult, HarnessConfig, ProjectKind, ProjectSelection
from .tdd_policy import find_cpp_files


class DiscoveryError(RuntimeError):
    pass


def _cmake_entry(source: Path) -> Path | None:
    for name in ("CMakePresets.json", "CMakeUserPresets.json", "CMakeLists.txt"):
        candidate = source / name
        if candidate.is_file():
            return candidate
    return None


def _single_msbuild(root: Path, explicit: Path | None) -> Path:
    if explicit is not None:
        if explicit.is_file() and explicit.suffix.lower() in {".sln", ".vcxproj"}:
            return explicit
        raise DiscoveryError(f"configured MSBuild project is invalid: {explicit}")
    solutions = sorted(root.glob("*.sln"))
    if len(solutions) > 1:
        raise DiscoveryError("multiple .sln files found; set msbuild.solution")
    if solutions:
        return solutions[0]
    projects = sorted(root.glob("*.vcxproj"))
    if len(projects) > 1:
        raise DiscoveryError("multiple .vcxproj files found; set msbuild.solution")
    if projects:
        return projects[0]
    raise DiscoveryError("no root .sln or .vcxproj was found")


def discover_project(root: Path, config: HarnessConfig) -> DiscoveryResult:
    root = root.resolve()
    if config.project_type == "cmake":
        entry = _cmake_entry(config.cmake.source_dir)
        if entry is None:
            raise DiscoveryError(
                f"no CMake project found in {config.cmake.source_dir}"
            )
        return DiscoveryResult(ProjectSelection(ProjectKind.CMAKE, entry))

    if config.project_type == "msbuild":
        project = _single_msbuild(root, config.msbuild.solution)
        return DiscoveryResult(ProjectSelection(ProjectKind.MSBUILD, project))

    entry = _cmake_entry(root)
    if entry is not None:
        return DiscoveryResult(ProjectSelection(ProjectKind.CMAKE, entry))

    solutions = sorted(root.glob("*.sln"))
    if len(solutions) > 1:
        raise DiscoveryError("multiple .sln files found; set msbuild.solution")
    if solutions:
        return DiscoveryResult(
            ProjectSelection(ProjectKind.MSBUILD, solutions[0])
        )

    projects = sorted(root.glob("*.vcxproj"))
    if len(projects) > 1:
        raise DiscoveryError("multiple .vcxproj files found; set msbuild.solution")
    if projects:
        return DiscoveryResult(
            ProjectSelection(ProjectKind.MSBUILD, projects[0])
        )

    cpp_files = find_cpp_files(root)
    if cpp_files:
        raise DiscoveryError(
            "C/C++ files exist but no CMakeLists.txt, preset, .sln, or .vcxproj "
            "was found; configure projectType and project path"
        )
    return DiscoveryResult(None, ())
