"""
This script contains functions needed for the core functionality of gitcrawl
"""
import os
import sys
import re
import subprocess
import yaml
import json
from tqdm import tqdm
import time
import pickle
from .helpers import extract_keys, enumerate_installation_candidates, menu, choose_custom_name

def findimports_to_dict(src_rep):
	"""
	This function executes 

	Parameters
	----------
	src_rep: str | list
		repository to be parsed by findimports

	Returns
	-------
	dict: 
		module-names found 
		
	"""
	findimports = subprocess.run(
		['findimports'] + src_rep, capture_output=True)

	output = {}
	current_line = ''
	for line in findimports.stdout.decode('utf-8').splitlines():
		stripped = line.strip()
		# omit empty lines
		if len(stripped) == 0:
			continue
		# line has no added spaces in front. Thus it must be a directory.
		if len(stripped) == len(line):
			# remove the double colon at the end
			current_line = stripped[:-1]
			output[current_line] = []
			continue
		# append package name
		# only include the module name, e.g only skimage instead of skimage.filters
		output[current_line].append(stripped.split('.')[0])

	output = {k: set(output[k]) for k in output}

	return output

def determine_disjunct_module_names(src_rep):
	"""
	Filter the modules found by findimports in such a way that returned modules meet following criteria:
	* the module should not be part of the python installation running within this environment
	* the module should be external (exclude self-written modules)
	
	Notes:
		arising problem: E.g pickle which ships with each python installation but is nowhere to be found in sys.modules if not explicitly loaded
		hotfix: use python_module_index which is generated by generate_python_module_list.py 

	Parameters
	----------
	src_rep : str or list
		repository to be parsed by findimports
		
	Returns
	-------
	list:
		list containing unique module-names meeting above criteria
	"""
	# findimports only allows us to find either all imports or only the internally used ones.
	# but we need only the external ones
	all_modules = findimports_to_dict([src_rep])
	internal_modules = findimports_to_dict(['-N', src_rep])
	external_modules = {k: [ module for module in all_modules[k] if module not in internal_modules[k] ] for k in all_modules}

	external_modules_list = []
	for values in external_modules.values():
		for v in values:
			external_modules_list.append(v)
	custom_modules_lst = set(external_modules_list)

	# Filter out python internal modules (hotfix)
	python_module_index_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'python_module_index.pickle')
	python_modules = pickle.load(open(python_module_index_path, 'rb'))
	disjunct_modules = [x for x in custom_modules_lst if x not in python_modules]

	#This should be a good approximation of all external imports.
	return disjunct_modules
	
def search_conda_channel(conda_channel, elem, leave_me_alone=False):
	"""
	Search package within different conda channels.

	Parameters
	----------
	conda_channel : str
		conda channel to be searched
	elem : str
		package-name to be searched
	leave_me_alone : bool, optional
		if True most of the interactive queries are skipped, by default False

	Returns
	-------
	str: 
		package-name | None
	bool: 
		skip? True | False
	"""
	try:
		#This is a workaround as listening to a keyboard doesen't work on macos due to security reasons
		#Running the program via sudo feels wrong
		if leave_me_alone:
			print(f"Search {elem} within {conda_channel}...")	
		else:	
			cancel = input(f"Search {elem} within {conda_channel}?  press [enter] | [c] to cancel: ")
			if cancel == "c":
				print("Canceled search within this channel.")
				return None, True

		cmd = f'conda search -c {conda_channel} --json {elem}'.split()

		#TODO find better suitable parameters if there is time, currently only the seconds make sense 
		with tqdm(unit='s', unit_scale=True, miniters=1, mininterval=0, maxinterval=1, desc=elem) as t:
			#elapsed_time = time.time() - start_time
			call = subprocess.run(cmd,capture_output=True, shell=False)
			for line in call.stdout:
				t.set_description("processing...")
				t.update()
				sys.stdout.flush()

		call.check_returncode()
		print(f"Installation candidate for {elem} found!")

		output_dict = json.loads(call.stdout)
		output_lst = extract_keys(output_dict)
		installation_candidates = enumerate_installation_candidates(output_lst)

		if len(installation_candidates) == 1:
			# Return only available choice
			return installation_candidates[0], True

		else:
			print(f"Found several installation candidates for {elem}")
			choice = menu(installation_candidates, elem)

			print(f"choice is: {choice}")

			if choice == "s":
				print("Skip searching this channel...")
				return None, False
			elif choice == "c":
				return None, True
			elif choice == "n":
				print("Implement adding custom package name here..")
				package_name = choose_custom_name(elem)
				return package_name, True
			else:
				return installation_candidates[int(choice)], True

	except subprocess.CalledProcessError:
		print(f'{elem} not found within {conda_channel}.')
		if leave_me_alone:
			print("Trying next channel...")			
			return None, False
		else:
			choice = input("Do you want to insert a custom name instead? [y/n](default:no) | [c] for cancel :")
			if choice.lower() == "y":
				package_name = choose_custom_name(elem)
				return package_name, True
			elif choice.lower() == "c":
				return None, True
			else:
				print("Trying next channel...")			
				return None, False


def search_pip( elem, leave_me_alone=False):
	"""
	Search package within pip. 

	Notes:
		This functions gets only executed if the package wasn't found in neither
		of the provided conda-channels

		This regex failed to work after pip-search was deactivated - kept here in case of future usage.
		cleaned_lst = [re.split("(?P<version-number>\s\((\d+\.?\w*){1,3}\))", sample)[0] for sample in output]

	Parameters
	----------
	conda_channel : str
		conda channel to be searched
	elem : str
		package-name to be searched
	leave_me_alone : bool, optional
		if True most of the interactive queries are skipped, by default False

	Returns
	-------
	str: 
		package-name | None
	bool: 
		skip? True | False
	"""
	#This is a workaround as listening to a keyboard doesn't work on macos due to security reasons
	#Running the program via sudo feels wrong
	if leave_me_alone:
		print(f"Search {elem} within pip...")	
	else:	
		cancel = input(f"Search {elem} within pip?  press [enter] | [c] to cancel: ")
		if cancel == "c":
			#print("Cancel search. Skip to next package...")
			return None, True

	cmd = f'pip_search {elem}'.split()
	sp_return_code = subprocess.check_call(cmd, shell=False)
	
	sp = subprocess.run(cmd, shell=False, check=True,
						capture_output=True, text=True)

	output = sp.stdout.splitlines()
	cleaned_lst = [ sample.split(" ")[0] for sample in output][3:-1]
	installation_candidates = enumerate_installation_candidates(cleaned_lst)

	if len(installation_candidates) == 1:
		# Return only available choice
		return installation_candidates[0], True

	#Why the except block failed to execute? Also a change after switching to pip_search? Currently removed
	elif len(installation_candidates) == 0:
		return None, False

	else:
		print(f"Found several installation candidates for {elem}")
		choice = menu(installation_candidates, elem)

		if choice == "c" or choice == "s":
			return None, True
		elif choice == "n":
			print("Implement adding custom package name here..")
			package_name = choose_custom_name(elem)
			return package_name, True
		else:
			return installation_candidates[int(choice)], True

def create_env(conda_lst, pip_lst, env_name, channel_lst):
	"""
	Creates a valid environment.yml

	Parameters
	----------
	conda_lst : list
		list of packages to be installed via conda
	pip_lst : list
		list of packages to be installed via conda
	env_name : str
		name of the resulting environment.yml
	channel_lst : list
		list of conda-channels that are to be searched
	"""
	dependencies = conda_lst
	name = env_name
	
	if pip_lst:
		dependencies.append({'pip': pip_lst})


	dict_file = {'name': env_name,
				 'channels': channel_lst,
				 'dependencies': dependencies
				 }

	with open(f'{env_name}.yml', 'w') as file:
		print("Creating new environment.yml...")
		yaml.dump(dict_file, file, sort_keys=False)