import ast
import glob
import os
import shutil
import unittest
from unittest.mock import patch

import frappe
from frappe.utils.boilerplate import make_boilerplate


class TestBoilerPlate(unittest.TestCase):
	@classmethod
	def setUpClass(cls):
		title = "Test App"
		description = "This app's description contains 'single quotes' and \"double quotes\"."
		publisher = "Test Publisher"
		email = "example@example.org"
		icon = ""  # empty -> default
		color = ""
		app_license = "MIT"

		cls.user_input = [
			title,
			description,
			publisher,
			email,
			icon,
			color,
			app_license,
		]

		cls.bench_path = frappe.utils.get_bench_path()
		cls.apps_dir = os.path.join(cls.bench_path, "apps")
		cls.app_names = ("test_app", "test_app_no_git")
		cls.gitignore_file = ".gitignore"
		cls.git_folder = ".git"

		cls.root_paths = [
			"requirements.txt",
			"README.md",
			"setup.py",
			"license.txt",
			cls.git_folder,
			cls.gitignore_file,
		]
		cls.paths_inside_app = [
			"__init__.py",
			"hooks.py",
			"patches.txt",
			"templates",
			"www",
			"config",
			"modules.txt",
			"public",
		]

	@classmethod
	def tearDownClass(cls):
		test_app_dirs = (os.path.join(cls.bench_path, "apps", app_name) for app_name in cls.app_names)
		for test_app_dir in test_app_dirs:
			if os.path.exists(test_app_dir):
				shutil.rmtree(test_app_dir)

	def test_create_app(self):
		with patch("builtins.input", side_effect=self.user_input):
			make_boilerplate(self.apps_dir, self.app_names[0])

		new_app_dir = os.path.join(self.bench_path, self.apps_dir, self.app_names[0])

		paths = self.get_paths(new_app_dir, self.app_names[0])
		for path in paths:
			self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {self.app_names[0]} app")

		self.check_parsable_python_files(new_app_dir)

	def test_create_app_without_git_init(self):
		with patch("builtins.input", side_effect=self.user_input):
			make_boilerplate(self.apps_dir, self.app_names[1], no_git=True)

		new_app_dir = os.path.join(self.apps_dir, self.app_names[1])

		paths = self.get_paths(new_app_dir, self.app_names[1])
		for path in paths:
			if os.path.basename(path) in (self.git_folder, self.gitignore_file):
				self.assertFalse(
					os.path.exists(path), msg=f"{path} shouldn't exist in {self.app_names[1]} app"
				)
			else:
				self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {self.app_names[1]} app")

		self.check_parsable_python_files(new_app_dir)

	def get_paths(self, app_dir, app_name):
		all_paths = list()

		for path in self.root_paths:
			all_paths.append(os.path.join(app_dir, path))

		all_paths.append(os.path.join(app_dir, app_name))

		for path in self.paths_inside_app:
			all_paths.append(os.path.join(app_dir, app_name, path))

		return all_paths

	def check_parsable_python_files(self, app_dir):
		# check if python files are parsable
		python_files = glob.glob(app_dir + "**/*.py", recursive=True)

		for python_file in python_files:
			with open(python_file) as p:
				try:
					ast.parse(p.read())
				except Exception as e:
					self.fail(f"Can't parse python file in new app: {python_file}\n" + str(e))
