import subprocess
import sys

from .tcr_console import console as c


class Dependency:
	import_name: str
	pip_name: str

	def __init__(self, *, import_as: str, pip_install_as: str = None) -> None:
		self.import_name = import_as
		self.pip_name = pip_install_as or import_as

	def install(self) -> bool:
		"""Returns bool if the installation succeeded or not."""
		try:
			subprocess.run([sys.executable, "-m", "pip", "install", self.pip_name], check=True)
		except subprocess.CalledProcessError:
			return False
		except Exception:
			return False
		else:
			return True


class DependencyEnsurer:
	deps: tuple[Dependency]
	auto_install: bool

	def __init__(self, *deps: Dependency, auto_install: bool = False) -> None:
		"""Allows libraries to have optional dependencies (I KNOW OF `lib[extra]` syntax..! - you should probably use that thing instead).

		Requires at least 1 dependency to work. (will do nothing if `deps=()`)

		auto_install: If true, will install any missing dependencies automatically, if False, a RuntimeError will be raised and a message will be displayed. Warning: This may heavily slow down the program and may crash if no internet connection is present to fetch the missing dependencies.
		"""
		self.deps = deps
		self.auto_install = auto_install

	def __call__(self, auto_install_override: bool | None = None) -> bool:
		"""Verify if this package's dependencies are installed."""

		auto_install = auto_install_override if auto_install_override is not None else self.auto_install

		missing_deps: list[Dependency] = []

		for dep in self.deps:
			try:
				__import__(dep.import_name)
			except ImportError:
				missing_deps.append(dep)

		def errored():
			c.error("The following dependencies are missing:")
			for dep in missing_deps:
				c.error(f"{dep.import_name!r} (pip install {dep.pip_name})")
			raise RuntimeError("Missing dependencies (see errors above for tips!!).")

		if missing_deps:
			if auto_install:
				for dep in missing_deps.copy():
					if not dep.install():
						errored()
					else:
						missing_deps.remove(dep)
			else:
				errored()
