import os
import json
import stat
import logging
import pathlib
from typing import Optional, Dict

from .hsm.fido import Fido2
from .models.disk_encryption import DiskEncryption
from .storage import storage
from .general import JSON, UNSAFE_JSON
from .output import log
from .exceptions import RequirementError


def configuration_sanity_check():
	disk_encryption: DiskEncryption = storage['arguments'].get('disk_encryption')
	if disk_encryption is not None and disk_encryption.hsm_device:
		if not Fido2.get_fido2_devices():
			raise RequirementError(
				f"In order to use HSM to pair with the disk encryption,"
				+ f" one needs to be accessible through /dev/hidraw* and support"
				+ f" the FIDO2 protocol. You can check this by running"
				+ f" 'systemd-cryptenroll --fido2-device=list'."
			)


class ConfigurationOutput:
	def __init__(self, config: Dict):
		"""
		Configuration output handler to parse the existing configuration data structure and prepare for output on the
		console and for saving it to configuration files

		:param config: A dictionary containing configurations (basically archinstall.arguments)
		:type config: Dict
		"""
		self._config = config
		self._user_credentials = {}
		self._disk_layout = None
		self._user_config = {}
		self._default_save_path = pathlib.Path(storage.get('LOG_PATH', '.'))
		self._user_config_file = 'user_configuration.json'
		self._user_creds_file = "user_credentials.json"
		self._disk_layout_file = "user_disk_layout.json"

		self._sensitive = ['!users']
		self._ignore = ['abort', 'install', 'config', 'creds', 'dry_run']

		self._process_config()

	@property
	def user_credentials_file(self):
		return self._user_creds_file

	@property
	def user_configuration_file(self):
		return self._user_config_file

	@property
	def disk_layout_file(self):
		return self._disk_layout_file

	def _process_config(self):
		for key in self._config:
			if key in self._sensitive:
				self._user_credentials[key] = self._config[key]
			elif key == 'disk_layouts':
				self._disk_layout = self._config[key]
			elif key in self._ignore:
				pass
			else:
				self._user_config[key] = self._config[key]

				if key == 'disk_encryption' and self._config[key]:  # special handling for encryption password
					self._user_credentials['encryption_password'] = self._config[key].encryption_password

	def user_config_to_json(self) -> str:
		return json.dumps({
			'config_version': storage['__version__'],  # Tells us what version was used to generate the config
			**self._user_config,  # __version__ will be overwritten by old version definition found in config
			'version': storage['__version__']
		}, indent=4, sort_keys=True, cls=JSON)

	def disk_layout_to_json(self) -> Optional[str]:
		if self._disk_layout:
			return json.dumps(self._disk_layout, indent=4, sort_keys=True, cls=JSON)
		return None

	def user_credentials_to_json(self) -> Optional[str]:
		if self._user_credentials:
			return json.dumps(self._user_credentials, indent=4, sort_keys=True, cls=UNSAFE_JSON)
		return None

	def show(self):
		print(_('\nThis is your chosen configuration:'))
		log(" -- Chosen configuration --", level=logging.DEBUG)

		user_conig = self.user_config_to_json()
		disk_layout = self.disk_layout_to_json()
		log(user_conig, level=logging.INFO)

		if disk_layout:
			log(disk_layout, level=logging.INFO)

		print()

	def _is_valid_path(self, dest_path :pathlib.Path) -> bool:
		if (not dest_path.exists()) or not (dest_path.is_dir()):
			log(
				'Destination directory {} does not exist or is not a directory,\n Configuration files can not be saved'.format(dest_path.resolve()),
				fg="yellow"
			)
			return False
		return True

	def save_user_config(self, dest_path :pathlib.Path = None):
		if self._is_valid_path(dest_path):
			target = dest_path / self._user_config_file

			with open(target, 'w') as config_file:
				config_file.write(self.user_config_to_json())

			os.chmod(str(dest_path / self._user_config_file), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)

	def save_user_creds(self, dest_path :pathlib.Path = None):
		if self._is_valid_path(dest_path):
			if user_creds := self.user_credentials_to_json():
				target = dest_path / self._user_creds_file

				with open(target, 'w') as config_file:
					config_file.write(user_creds)

				os.chmod(str(target), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)

	def save_disk_layout(self, dest_path :pathlib.Path = None):
		if self._is_valid_path(dest_path):
			if disk_layout := self.disk_layout_to_json():
				target = dest_path / self._disk_layout_file

				with target.open('w') as config_file:
					config_file.write(disk_layout)

				os.chmod(str(target), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)

	def save(self, dest_path :pathlib.Path = None):
		if not dest_path:
			dest_path = self._default_save_path

		if self._is_valid_path(dest_path):
			self.save_user_config(dest_path)
			self.save_user_creds(dest_path)
			self.save_disk_layout(dest_path)
