import abc
import os.path
import re
from pathlib import Path
from sys import platform


from safety.tool.pip import Pip
from safety.tool.poetry import Poetry
from safety.tool.npm import Npm

from typing import Any, TYPE_CHECKING, Optional

from safety.tool.uv.main import Uv

if TYPE_CHECKING:
    pass


def is_os_supported():
    return platform in ["linux", "linux2", "darwin", "win32"]


class BuildFileConfigurator(abc.ABC):
    @abc.abstractmethod
    def is_supported(self, file: Path) -> bool:
        """
        Returns whether a specific file is supported by this class.
        Args:
            file (str): The file to check.
        Returns:
            bool: Whether the file is supported by this class.
        """
        pass

    @abc.abstractmethod
    def configure(
        self, file: Path, org_slug: Optional[str], project_id: Optional[str]
    ) -> Optional[Path]:
        """
        Configures specific file.
        Args:
            file (str): The file to configure.
            org_slug (str): The organization slug.
            project_id (str): The project identifier.
        """
        pass


class PipRequirementsConfigurator(BuildFileConfigurator):
    __file_name_pattern = re.compile("^([a-zA-Z_-]+)?requirements([a-zA-Z_-]+)?.txt$")

    def is_supported(self, file: Path) -> bool:
        return self.__file_name_pattern.match(os.path.basename(file)) is not None

    def configure(
        self, file: Path, org_slug: Optional[str], project_id: Optional[str]
    ) -> None:
        Pip.configure_requirements(file, org_slug, project_id)


class PoetryPyprojectConfigurator(BuildFileConfigurator):
    __file_name_pattern = re.compile("^pyproject.toml$")

    def is_supported(self, file: Path) -> bool:
        return self.__file_name_pattern.match(
            os.path.basename(file)
        ) is not None and Poetry.is_poetry_project_file(file)

    def configure(
        self, file: Path, org_slug: Optional[str], project_id: Optional[str]
    ) -> Optional[Path]:
        if self.is_supported(file):
            return Poetry.configure_pyproject(file, org_slug, project_id)  # type: ignore
        return None


# TODO: Review if we should move this/hook up this into interceptors.
class ToolConfigurator(abc.ABC):
    @abc.abstractmethod
    def configure(self, org_slug: Optional[str]) -> Any:
        """
        Configures specific tool.
        Args:
            org_slug (str): The organization slug.
        """
        pass

    @abc.abstractmethod
    def reset(self) -> None:
        """
        Resets specific tool.
        """
        pass


class PipConfigurator(ToolConfigurator):
    def configure(self, org_slug: Optional[str]) -> Optional[Path]:
        return Pip.configure_system(org_slug)

    def reset(self) -> None:
        Pip.reset_system()


class PoetryConfigurator(ToolConfigurator):
    """
    Configures poetry system is not supported.
    """

    def configure(self, org_slug: Optional[str]) -> Optional[Path]:
        return None

    def reset(self) -> None:
        return None


class UvConfigurator(ToolConfigurator):
    def configure(self, org_slug: Optional[str]) -> Optional[Path]:
        return Uv.configure_system(org_slug)

    def reset(self) -> None:
        Uv.reset_system()


class UvPyprojectConfigurator(BuildFileConfigurator):
    __file_name_pattern = re.compile("^uv.lock$")

    def is_supported(self, file: Path) -> bool:
        return (
            self.__file_name_pattern.match(os.path.basename(file)) is not None
            and Path("pyproject.toml").exists()
        )

    def configure(
        self, file: Path, org_slug: Optional[str], project_id: Optional[str]
    ) -> Optional[Path]:
        if self.is_supported(file):
            return Uv.configure_pyproject(Path("pyproject.toml"), org_slug, project_id)
        return None


class NpmConfigurator(ToolConfigurator):
    def configure(self, org_slug: Optional[str]) -> Optional[Path]:
        return Npm.configure_system(org_slug)

    def reset(self) -> None:
        Npm.reset_system()


class NpmProjectConfigurator(BuildFileConfigurator):
    __file_name_pattern = re.compile("^package.json$")

    def is_supported(self, file: Path) -> bool:
        return self.__file_name_pattern.match(os.path.basename(file)) is not None

    def configure(
        self, file: Path, org_slug: Optional[str], project_id: Optional[str]
    ) -> Optional[Path]:
        if self.is_supported(file):
            return Npm.configure_project(file, org_slug, project_id)
        return None
