"""Shared primitives for Polytropos."""

import logging
import os
import tomllib
from pathlib import Path
from typing import Any

from packaging.specifiers import SpecifierSet
from packaging.version import Version
from pathspec import PathSpec

from ..ph import _odoo

_logger = logging.getLogger(__name__)


def get_odoo_release(
    config_settings: dict[str, Any] | None = None,
    pyproject_data: dict[str, Any] | None = None,
) -> str:
    """Get the Odoo release to use for building.

    Resolution order:
    1. config_settings["odoo_release"]
    2. ODOO_RELEASE environment variable
    3. odoo.release.series (if odoo installed)
    4. tool.polytropos.default_odoo_release (if pyproject_data provided)

    Args:
        config_settings: PEP 517 config_settings dictionary
        pyproject_data: Parsed pyproject.toml (optional, for default fallback)

    Returns:
        Odoo release string (e.g., "17.0")

    Raises:
        ValueError: If release cannot be determined
    """
    if config_settings and (release := config_settings.get("odoo_release")):
        return str(release)

    if release := os.environ.get("ODOO_RELEASE"):
        return release

    try:
        return str(_odoo().release.series)
    except (ImportError, AttributeError):
        _logger.debug("Could not detect Odoo release from installed package")

    if pyproject_data:
        polytropos_config = pyproject_data.get("tool", {}).get("polytropos", {})
        if default_release := polytropos_config.get("default_odoo_release"):
            return default_release

    raise ValueError(
        "Odoo release not specified. Use --config-settings odoo_release=19.0, "
        "set ODOO_RELEASE environment variable, set "
        "[tool.polytropos].default_odoo_release in pyproject.toml, or make "
        "the odoo package importable at build time."
    )


def build_full_version(module_version: str, odoo_release: str) -> str:
    """Build the full version string for the module.

    Combines the Odoo release (A.B) with the module version (X.Y.Z)
    to form A.B.X.Y.Z (5+ components, required by manifestoo-core).

    Args:
        module_version: Module version (e.g., "1.0.0", "1.2.3")
        odoo_release: Odoo release (e.g., "17.0")

    Returns:
        Full version (e.g., "17.0.1.0.0", "18.0.1.2.3")

    Raises:
        ValueError: If module version has fewer than 3 components
    """
    release_version = Version(odoo_release)
    module_version_obj = Version(module_version)

    release_parts = [release_version.major, release_version.minor]

    mod_parts: list[int] = []
    if hasattr(module_version_obj, "release") and isinstance(
        module_version_obj.release, tuple
    ):
        mod_parts.extend(
            part for part in module_version_obj.release if part is not None
        )

    if not mod_parts:
        mod_parts = [module_version_obj.major, module_version_obj.minor]

    if len(mod_parts) < 3:
        raise ValueError(
            f"Module version must have at least 3 components (X.Y.Z), got {module_version}"
        )

    return ".".join(str(p) for p in release_parts + mod_parts)


def get_pyproject_data(root: Path) -> dict[str, Any]:
    """Load and parse pyproject.toml.

    Args:
        root: Path to the project root

    Returns:
        Parsed pyproject.toml as dictionary

    Raises:
        ValueError: If pyproject.toml not found
    """
    pyproject_path = root / "pyproject.toml"
    if not pyproject_path.exists():
        raise ValueError("pyproject.toml not found")

    with open(pyproject_path, "rb") as f:
        return tomllib.load(f)


def parse_release(release: str) -> Version:
    """Parse release string to Version object.

    Args:
        release: Release string (e.g., "17.0")

    Returns:
        Version object
    """
    normalized = release
    if "." not in normalized:
        normalized = f"{normalized}.0"
    return Version(normalized)


def check_release_compatibility(
    odoo_release: str,
    pyproject_data: dict[str, Any],
) -> None:
    """Check if Odoo release is compatible with project.dependencies constraints.

    Args:
        odoo_release: Odoo release version (e.g., "17.0")
        pyproject_data: Parsed pyproject.toml

    Raises:
        ValueError: If release is incompatible with constraints
    """
    from packaging.requirements import Requirement

    dependencies = pyproject_data.get("project", {}).get("dependencies", [])
    for dep in dependencies:
        req = Requirement(dep)
        if req.name == "odoo":
            if odoo_release not in req.specifier:
                raise ValueError(
                    f"Cannot build for Odoo {odoo_release}: project requires odoo{req.specifier}"
                )
            return


def matches_release_specifier(release_version: Version, specifier: str | None) -> bool:
    """Check if release matches the given PEP 440 specifier.

    Args:
        release_version: Parsed Version object
        specifier: PEP 440 version specifier (e.g., ">=18, <20")

    Returns:
        True if release matches specifier
    """
    if specifier is None or specifier == "":
        return True
    try:
        spec = SpecifierSet(specifier)
        return release_version in spec
    except (ValueError, TypeError) as exc:
        import logging

        logging.getLogger(__name__).debug(
            "Invalid version specifier '%s': %s", specifier, exc
        )
        return False


def get_exclusion_patterns(root: Path) -> set[str]:
    """Get file patterns to exclude from builds.

    Combines default patterns with .gitignore if present.

    Args:
        root: Project root directory

    Returns:
        Set of glob patterns to exclude
    """
    DEFAULT_EXCLUDES = {
        ".git",
        ".github",
        "__pycache__",
        ".eggs",
        "*.pyc",
        "*.pyo",
        ".pytest_cache",
        ".mypy_cache",
        ".ruff_cache",
        "node_modules",
    }

    patterns = DEFAULT_EXCLUDES.copy()

    gitignore = root / ".gitignore"
    if gitignore.exists():
        gitignore_spec = PathSpec.from_lines("gitwildmatch", gitignore.read_text())
        gitignore_patterns = {p.pattern for p in gitignore_spec.patterns}  # type: ignore[attr-defined]
        return patterns | gitignore_patterns

    return patterns
