import glob
import pathlib
import logging
import json
from dataclasses import dataclass
from typing import Optional, List, Dict, Any, Iterator, TYPE_CHECKING

from ..exceptions import SysCallError
from ..general import SysCommand
from ..output import log

if TYPE_CHECKING:
	from .btrfs import BtrfsSubvolumeInfo

@dataclass
class MapperDev:
	mappername :str

	@property
	def name(self):
		return self.mappername

	@property
	def path(self):
		return f"/dev/mapper/{self.mappername}"

	@property
	def part_uuid(self):
		return self.partition.part_uuid

	@property
	def partition(self):
		from .helpers import uevent, get_parent_of_partition
		from .partition import Partition
		from .blockdevice import BlockDevice

		for mapper in glob.glob('/dev/mapper/*'):
			path_obj = pathlib.Path(mapper)
			if path_obj.name == self.mappername and pathlib.Path(mapper).is_symlink():
				dm_device = (pathlib.Path("/dev/mapper/") / path_obj.readlink()).resolve()

				for slave in glob.glob(f"/sys/class/block/{dm_device.name}/slaves/*"):
					partition_belonging_to_dmcrypt_device = pathlib.Path(slave).name

					try:
						uevent_data = SysCommand(f"blkid -o export /dev/{partition_belonging_to_dmcrypt_device}").decode()
					except SysCallError as error:
						log(f"Could not get information on device /dev/{partition_belonging_to_dmcrypt_device}: {error}", level=logging.ERROR, fg="red")

					information = uevent(uevent_data)
					block_device = BlockDevice(get_parent_of_partition('/dev/' / pathlib.Path(information['DEVNAME'])))

					return Partition(information['DEVNAME'], block_device=block_device)

		raise ValueError(f"Could not convert {self.mappername} to a real dm-crypt device")

	@property
	def mountpoint(self) -> Optional[pathlib.Path]:
		try:
			data = json.loads(SysCommand(f"findmnt --json -R {self.path}").decode())
			for filesystem in data['filesystems']:
				return pathlib.Path(filesystem.get('target'))

		except SysCallError as error:
			# Not mounted anywhere most likely
			log(f"Could not locate mount information for {self.path}: {error}", level=logging.WARNING, fg="yellow")
			pass

		return None

	@property
	def mountpoints(self) -> List[Dict[str, Any]]:
		return [obj['target'] for obj in self.mount_information]

	@property
	def mount_information(self) -> List[Dict[str, Any]]:
		from .helpers import find_mountpoint
		return [{**obj, 'target' : pathlib.Path(obj.get('target', '/dev/null'))} for obj in find_mountpoint(self.path)]

	@property
	def filesystem(self) -> Optional[str]:
		from .helpers import get_filesystem_type
		return get_filesystem_type(self.path)

	@property
	def subvolumes(self) -> Iterator['BtrfsSubvolumeInfo']:
		from .btrfs import subvolume_info_from_path

		for mountpoint in self.mount_information:
			if target := mountpoint.get('target'):
				if subvolume := subvolume_info_from_path(pathlib.Path(target)):
					yield subvolume
