import os
import shutil
import unittest
from contextlib import contextmanager
from pathlib import Path

import jingrow
from jingrow import scrub
from jingrow.core.pagetype.pagetype.test_pagetype import new_pagetype
from jingrow.custom.pagetype.custom_field.custom_field import create_custom_field
from jingrow.custom.pagetype.property_setter.property_setter import make_property_setter
from jingrow.model.meta import trim_table
from jingrow.modules import export_customizations, export_module_json, get_module_path
from jingrow.modules.utils import export_pg, sync_customizations
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils import now_datetime


def write_file(path, content):
	with open(path, "w") as f:
		f.write(content)


def delete_file(path):
	if path:
		os.remove(path)


def delete_path(path):
	if path:
		shutil.rmtree(path, ignore_errors=True)


class TestUtils(JingrowTestCase):
	def setUp(self):
		self._dev_mode = jingrow.local.conf.developer_mode
		jingrow.local.conf.developer_mode = True

	def tearDown(self):
		jingrow.db.rollback()
		jingrow.local.conf.developer_mode = self._dev_mode
		jingrow.local.flags.pop("in_import", None)

	def test_export_module_json_no_export(self):
		jingrow.local.flags.in_import = True
		pg = jingrow.get_last_pg("PageType")
		self.assertIsNone(export_module_json(pg=pg, is_standard=True, module=pg.module))

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	def test_export_module_json(self):
		pg = jingrow.get_last_pg("PageType", {"issingle": 0, "custom": 0})
		export_pg_path = os.path.join(
			get_module_path(pg.module),
			scrub(pg.pagetype),
			scrub(pg.name),
			f"{scrub(pg.name)}.json",
		)
		with open(export_pg_path) as f:
			export_pg_before = jingrow.parse_json(f.read())

		last_modified_before = os.path.getmtime(export_pg_path)
		self.addCleanup(write_file, path=export_pg_path, content=jingrow.as_json(export_pg_before))

		jingrow.flags.in_import = False
		jingrow.conf.developer_mode = True
		export_path = export_module_json(pg=pg, is_standard=True, module=pg.module)

		last_modified_after = os.path.getmtime(export_pg_path)

		with open(f"{export_path}.json") as f:
			jingrow.parse_json(f.read())  # export_pg_after

		self.assertTrue(last_modified_after > last_modified_before)

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	def test_export_customizations(self):
		with note_customizations():
			file_path = export_customizations(module="Custom", pagetype="Note")
			self.addCleanup(delete_file, path=file_path)
			self.assertTrue(file_path.endswith("/custom/custom/note.json"))
			self.assertTrue(os.path.exists(file_path))

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	def test_sync_customizations(self):
		with note_customizations() as (custom_field, property_setter):
			file_path = export_customizations(module="Custom", pagetype="Note", sync_on_migrate=True)
			custom_field.db_set("modified", now_datetime())
			custom_field.reload()

			# Untracked property setter
			custom_prop_setter = make_property_setter(
				"Note", fieldname="content", property="bold", value="1", property_type="Check"
			)

			self.assertTrue(file_path.endswith("/custom/custom/note.json"))
			self.assertTrue(os.path.exists(file_path))
			last_modified_before = custom_field.modified

			sync_customizations(app="jingrow")
			self.assertTrue(property_setter.pagetype, property_setter.name)
			self.assertTrue(custom_prop_setter.pagetype, custom_prop_setter.name)

			self.assertTrue(file_path.endswith("/custom/custom/note.json"))
			self.assertTrue(os.path.exists(file_path))
			custom_field.reload()
			last_modified_after = custom_field.modified

			self.assertNotEqual(last_modified_after, last_modified_before)
			self.addCleanup(delete_file, path=file_path)

	def test_reload_pg(self):
		jingrow.db.set_value("PageType", "Note", "migration_hash", "", update_modified=False)
		self.assertFalse(jingrow.db.get_value("PageType", "Note", "migration_hash"))
		jingrow.db.set_value(
			"PageField",
			{"parent": "Note", "fieldname": "title"},
			"fieldtype",
			"Text",
			update_modified=False,
		)
		self.assertEqual(
			jingrow.db.get_value("PageField", {"parent": "Note", "fieldname": "title"}, "fieldtype"),
			"Text",
		)
		jingrow.reload_pagetype("Note")
		self.assertEqual(
			jingrow.db.get_value("PageField", {"parent": "Note", "fieldname": "title"}, "fieldtype"),
			"Data",
		)
		self.assertTrue(jingrow.db.get_value("PageType", "Note", "migration_hash"))

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	def test_export_pg(self):
		note = jingrow.new_pg("Note")
		note.title = jingrow.generate_hash(length=10)
		note.save()
		export_pg(pagetype="Note", name=note.name)
		exported_pg_path = Path(
			jingrow.get_app_path("jingrow", "desk", "note", note.name, f"{note.name}.json")
		)
		self.assertTrue(os.path.exists(exported_pg_path))
		self.addCleanup(delete_path, path=exported_pg_path.parent.parent)

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	def test_make_boilerplate(self):
		with temp_pagetype() as pagetype:
			scrubbed = jingrow.scrub(pagetype.name)
			path = jingrow.get_app_path("jingrow", "core", "pagetype", scrubbed, f"{scrubbed}.json")
			self.assertFalse(os.path.exists(path))
			pagetype.custom = False
			pagetype.save()
			self.assertTrue(os.path.exists(path))


@contextmanager
def temp_pagetype():
	try:
		pagetype = new_pagetype().insert()
		yield pagetype
	finally:
		pagetype.delete(force=True)
		jingrow.db.sql_ddl(f"DROP TABLE `tab{pagetype.name}`")


@contextmanager
def note_customizations():
	try:
		df = {
			"fieldname": "test_export_customizations_field",
			"label": "Custom Data Field",
			"fieldtype": "Data",
		}
		custom_field = create_custom_field("Note", df=df)

		property_setter = make_property_setter(
			"Note", fieldname="content", property="bold", value="1", property_type="Check"
		)
		yield custom_field, property_setter
	finally:
		custom_field.delete()
		property_setter.delete()
		trim_table("Note", dry_run=False)
		delete_path(jingrow.get_module_path("Desk", "Note"))
