from typing import Dict, Optional, Any, TYPE_CHECKING, List

from ..menu.abstract_menu import Selector, AbstractSubMenu
from ..menu.menu import MenuSelectionType
from ..menu.table_selection_menu import TableMenu
from ..models.disk_encryption import EncryptionType, DiskEncryption
from ..user_interaction.partitioning_conf import current_partition_layout
from ..user_interaction.utils import get_password
from ..menu import Menu
from ..general import secret
from ..hsm.fido import Fido2Device, Fido2

if TYPE_CHECKING:
	_: Any


class DiskEncryptionMenu(AbstractSubMenu):
	def __init__(self, data_store: Dict[str, Any], preset: Optional[DiskEncryption], disk_layouts: Dict[str, Any]):
		if preset:
			self._preset = preset
		else:
			self._preset = DiskEncryption()

		self._disk_layouts = disk_layouts
		super().__init__(data_store=data_store)

	def _setup_selection_menu_options(self):
		self._menu_options['encryption_password'] = \
			Selector(
				_('Encryption password'),
				lambda x: select_encrypted_password(),
				display_func=lambda x: secret(x) if x else '',
				default=self._preset.encryption_password,
				enabled=True
			)
		self._menu_options['encryption_type'] = \
			Selector(
				_('Encryption type'),
				func=lambda preset: select_encryption_type(preset),
				display_func=lambda x: EncryptionType.type_to_text(x) if x else None,
				dependencies=['encryption_password'],
				default=self._preset.encryption_type,
				enabled=True
			)
		self._menu_options['partitions'] = \
			Selector(
				_('Partitions'),
				func=lambda preset: select_partitions_to_encrypt(self._disk_layouts, preset),
				display_func=lambda x: f'{sum([len(y) for y in x.values()])} {_("Partitions")}' if x else None,
				dependencies=['encryption_password'],
				default=self._preset.partitions,
				preview_func=self._prev_disk_layouts,
				enabled=True
			)
		self._menu_options['HSM'] = \
			Selector(
				description=_('Use HSM to unlock encrypted drive'),
				func=lambda preset: select_hsm(preset),
				display_func=lambda x: self._display_hsm(x),
				dependencies=['encryption_password'],
				default=self._preset.hsm_device,
				enabled=True
			)

	def run(self, allow_reset: bool = True) -> Optional[DiskEncryption]:
		super().run(allow_reset=allow_reset)

		if self._data_store.get('encryption_password', None):
			return DiskEncryption(
				encryption_password=self._data_store.get('encryption_password', None),
				encryption_type=self._data_store['encryption_type'],
				partitions=self._data_store.get('partitions', None),
				hsm_device=self._data_store.get('HSM', None)
			)

		return None

	def _display_hsm(self, device: Optional[Fido2Device]) -> Optional[str]:
		if device:
			return device.manufacturer

		if not Fido2.get_fido2_devices():
			return str(_('No HSM devices available'))
		return None

	def _prev_disk_layouts(self) -> Optional[str]:
		selector = self._menu_options['partitions']
		if selector.has_selection():
			partitions: Dict[str, Any] = selector.current_selection

			all_partitions = []
			for parts in partitions.values():
				all_partitions += parts

			output = str(_('Partitions to be encrypted')) + '\n'
			output += current_partition_layout(all_partitions, with_title=False)
			return output.rstrip()
		return None


def select_encryption_type(preset: EncryptionType) -> Optional[EncryptionType]:
	title = str(_('Select disk encryption option'))
	options = [
		# _type_to_text(EncryptionType.FullDiskEncryption),
		EncryptionType.type_to_text(EncryptionType.Partition)
	]

	preset_value = EncryptionType.type_to_text(preset)
	choice = Menu(title, options, preset_values=preset_value).run()

	match choice.type_:
		case MenuSelectionType.Reset: return None
		case MenuSelectionType.Skip: return preset
		case MenuSelectionType.Selection: return EncryptionType.text_to_type(choice.value)  # type: ignore


def select_encrypted_password() -> Optional[str]:
	if passwd := get_password(prompt=str(_('Enter disk encryption password (leave blank for no encryption): '))):
		return passwd
	return None


def select_hsm(preset: Optional[Fido2Device] = None) -> Optional[Fido2Device]:
	title = _('Select a FIDO2 device to use for HSM')
	fido_devices = Fido2.get_fido2_devices()

	if fido_devices:
		choice = TableMenu(title, data=fido_devices).run()
		match choice.type_:
			case MenuSelectionType.Reset:
				return None
			case MenuSelectionType.Skip:
				return preset
			case MenuSelectionType.Selection:
				return choice.value  # type: ignore

	return None


def select_partitions_to_encrypt(disk_layouts: Dict[str, Any], preset: Dict[str, Any]) -> Dict[str, Any]:
	# If no partitions was marked as encrypted, but a password was supplied and we have some disks to format..
	# Then we need to identify which partitions to encrypt. This will default to / (root).
	all_partitions = []
	for blockdevice in disk_layouts.values():
		if partitions := blockdevice.get('partitions'):
			partitions = [p for p in partitions if p['mountpoint'] != '/boot']
			all_partitions += partitions

	if all_partitions:
		title = str(_('Select which partitions to encrypt'))
		partition_table = current_partition_layout(all_partitions, with_title=False).strip()

		choice = TableMenu(
			title,
			table_data=(all_partitions, partition_table),
			multi=True
		).run()

		match choice.type_:
			case MenuSelectionType.Reset:
				return {}
			case MenuSelectionType.Skip:
				return preset
			case MenuSelectionType.Selection:
				selections: List[Any] = choice.value  # type: ignore
				partitions = {}

				for path, device in disk_layouts.items():
					for part in selections:
						if part in device.get('partitions', []):
							partitions.setdefault(path, []).append(part)

				return partitions
	return {}
