from __future__ import annotations

import ipaddress
import logging
from typing import Any, Optional, TYPE_CHECKING, List, Union, Dict

from ..menu.menu import MenuSelectionType
from ..menu.text_input import TextInput
from ..models.network_configuration import NetworkConfiguration, NicType

from ..networking import list_interfaces
from ..menu import Menu
from ..output import log, FormattedOutput
from ..menu.list_manager import ListManager

if TYPE_CHECKING:
	_: Any


class ManualNetworkConfig(ListManager):
	"""
	subclass of ListManager for the managing of network configurations
	"""

	def __init__(self, prompt: str, ifaces: List[NetworkConfiguration]):
		self._actions = [
			str(_('Add interface')),
			str(_('Edit interface')),
			str(_('Delete interface'))
		]

		super().__init__(prompt, ifaces, [self._actions[0]], self._actions[1:])

	def reformat(self, data: List[NetworkConfiguration]) -> Dict[str, Optional[NetworkConfiguration]]:
		table = FormattedOutput.as_table(data)
		rows = table.split('\n')

		# these are the header rows of the table and do not map to any User obviously
		# we're adding 2 spaces as prefix because the menu selector '> ' will be put before
		# the selectable rows so the header has to be aligned
		display_data: Dict[str, Optional[NetworkConfiguration]] = {f'  {rows[0]}': None, f'  {rows[1]}': None}

		for row, iface in zip(rows[2:], data):
			row = row.replace('|', '\\|')
			display_data[row] = iface

		return display_data

	def selected_action_display(self, iface: NetworkConfiguration) -> str:
		return iface.iface if iface.iface else ''

	def handle_action(self, action: str, entry: Optional[NetworkConfiguration], data: List[NetworkConfiguration]):
		if action == self._actions[0]:  # add
			iface_name = self._select_iface(data)
			if iface_name:
				iface = NetworkConfiguration(NicType.MANUAL, iface=iface_name)
				iface = self._edit_iface(iface)
				data += [iface]
		elif entry:
			if action == self._actions[1]:  # edit interface
				data = [d for d in data if d.iface != entry.iface]
				data.append(self._edit_iface(entry))
			elif action == self._actions[2]:  # delete
				data = [d for d in data if d != entry]

		return data

	def _select_iface(self, data: List[NetworkConfiguration]) -> Optional[Any]:
		all_ifaces = list_interfaces().values()
		existing_ifaces = [d.iface for d in data]
		available = set(all_ifaces) - set(existing_ifaces)
		choice = Menu(str(_('Select interface to add')), list(available), skip=True).run()

		if choice.type_ == MenuSelectionType.Skip:
			return None

		return choice.value

	def _edit_iface(self, edit_iface: NetworkConfiguration):
		iface_name = edit_iface.iface
		modes = ['DHCP (auto detect)', 'IP (static)']
		default_mode = 'DHCP (auto detect)'

		prompt = _('Select which mode to configure for "{}" or skip to use default mode "{}"').format(iface_name, default_mode)
		mode = Menu(prompt, modes, default_option=default_mode, skip=False).run()

		if mode.value == 'IP (static)':
			while 1:
				prompt = _('Enter the IP and subnet for {} (example: 192.168.0.5/24): ').format(iface_name)
				ip = TextInput(prompt, edit_iface.ip).run().strip()
				# Implemented new check for correct IP/subnet input
				try:
					ipaddress.ip_interface(ip)
					break
				except ValueError:
					log("You need to enter a valid IP in IP-config mode.", level=logging.WARNING, fg='red')

			# Implemented new check for correct gateway IP address
			gateway = None

			while 1:
				gateway = TextInput(
					_('Enter your gateway (router) IP address or leave blank for none: '),
					edit_iface.gateway
				).run().strip()
				try:
					if len(gateway) > 0:
						ipaddress.ip_address(gateway)
					break
				except ValueError:
					log("You need to enter a valid gateway (router) IP address.", level=logging.WARNING, fg='red')

			if edit_iface.dns:
				display_dns = ' '.join(edit_iface.dns)
			else:
				display_dns = None
			dns_input = TextInput(_('Enter your DNS servers (space separated, blank for none): '), display_dns).run().strip()

			dns = []
			if len(dns_input):
				dns = dns_input.split(' ')

			return NetworkConfiguration(NicType.MANUAL, iface=iface_name, ip=ip, gateway=gateway, dns=dns, dhcp=False)
		else:
			# this will contain network iface names
			return NetworkConfiguration(NicType.MANUAL, iface=iface_name)


def ask_to_configure_network(
	preset: Union[NetworkConfiguration, List[NetworkConfiguration]]
) -> Optional[NetworkConfiguration | List[NetworkConfiguration]]:
	"""
		Configure the network on the newly installed system
	"""
	network_options = {
		'none': str(_('No network configuration')),
		'iso_config': str(_('Copy ISO network configuration to installation')),
		'network_manager': str(_('Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)')),
		'manual': str(_('Manual configuration'))
	}
	# for this routine it's easier to set the cursor position rather than a preset value
	cursor_idx = None

	if preset and not isinstance(preset, list):
		if preset.type == 'iso_config':
			cursor_idx = 0
		elif preset.type == 'network_manager':
			cursor_idx = 1

	warning = str(_('Are you sure you want to reset this setting?'))

	choice = Menu(
		_('Select one network interface to configure'),
		list(network_options.values()),
		cursor_index=cursor_idx,
		sort=False,
		allow_reset=True,
		allow_reset_warning_msg=warning
	).run()

	match choice.type_:
		case MenuSelectionType.Skip: return preset
		case MenuSelectionType.Reset: return None

	if choice.value == network_options['none']:
		return None
	elif choice.value == network_options['iso_config']:
		return NetworkConfiguration(NicType.ISO)
	elif choice.value == network_options['network_manager']:
		return NetworkConfiguration(NicType.NM)
	elif choice.value == network_options['manual']:
		preset_ifaces = preset if isinstance(preset, list) else []
		return ManualNetworkConfig('Configure interfaces', preset_ifaces).run()

	return preset
