"""
Dropping resources.  For now, you can only drop entire RDs.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


from gavo import api
from gavo import base
from gavo import utils
from gavo.protocols import obscore
from gavo.protocols import tap
from gavo.rsc import data
from gavo.user import common


def _do_dropTable(tableName, conn):
	"""deletes rows generated from tableName from the DC's metadata
	(and tableName itself).
	"""
	q = base.UnmanagedQuerier(conn)
	for metaTableName, columnName in [
			("dc.tablemeta", "tableName"),
			("dc.simple_col_stats", "tableName"),
			("ivoa._obscoresources", "tableName"),
			("tap_schema.tables", "table_name"),
			("tap_schema.keys", "from_table"),
			("tap_schema.keys", "target_table"),
			("tap_schema.columns", "table_name")]:
		if q.getTableType(metaTableName) is not None:
			q.connection.execute("delete from %s where %s=%%(tableName)s"%(
				metaTableName, columnName),
				{"tableName": tableName})

	if q.getTableType(tableName) is not None:
		q.dropTable(tableName, cascade=True)


def dropTable():
	"""tries to "manually" purge a table from the DC's memories.

	This is a "toplevel" function intended to be called by cli directly.
	"""
	def parseCmdline():
		from argparse import ArgumentParser
		parser = ArgumentParser(
			description="Removes all traces of the named table within the DC.")
		parser.add_argument("tablename", help="The name of the table to drop,"
		 	" including the schema name.", nargs="+")
		return parser.parse_args()
	
	opts = parseCmdline()
	
	conn = base.getDBConnection("feed")
	for tableName in opts.tablename:
		_do_dropTable(tableName, conn)
	conn.execute("DELETE FROM dc.products WHERE sourcetable=%(t)s",
		{'t': tableName})
	obscore.restoreObscore(conn)
	conn.commit()


def _do_dropRD(opts, rdId, connection, selectedIds=()):
	"""drops the data and services defined in the RD selected by rdId.
	"""
	try:
		rd = api.getReferencedElement(rdId, forceType=api.RD)
	except api.NotFoundError:
		if opts.force:
			rd = None
		else:
			raise

	if opts.force or rd is None:

		# If the RD doesn't exist any more, just manually purge it
		# from wherever it could have been mentioned.
		for tableName in ["dc.tablemeta", "tap_schema.tables",
				"tap_schema.columns", "tap_schema.keys", "tap_schema.key_columns",
				"dc.resources", "dc.interfaces", "dc.sets", "dc.subjects",
				"dc.authors", "dc.res_dependencies", "ivoa._obscoresources",
				"dc.biblinks", "dc.rdmeta"]:
			if base.UnmanagedQuerier(connection
					).getTableType(tableName) is not None:
				connection.execute(
					"delete from %s where sourceRd=%%(sourceRD)s"%tableName,
					{"sourceRD": rdId})

	else:
		# use normalised rd id from here on
		rdId = rd.sourceId
	
		if opts.dropAll:
			dds = rd.dds
		else:
			dds = common.getPertainingDDs(rd, selectedIds)

		parseOptions = api.getParseOptions(systemImport=opts.systemImport)

		dgraph = data.DDDependencyGraph(dds, spanRDs=False)
		dropSequence = dgraph.getDestroySequence()
		toDrop = set(dds)|set(dropSequence)
		for dd in dropSequence:
			api.Data.drop(dd, connection=connection,
				parseOptions=parseOptions)
			toDrop.remove(dd)
		for dd in toDrop:
			api.Data.drop(dd, connection=connection,
				parseOptions=parseOptions)

		if not selectedIds or opts.dropAll:
			from gavo.registry import servicelist
			servicelist.cleanServiceTablesFor(rd, connection)
			tap.unpublishFromTAP(rd, connection)

			# Against -f, we do *not* drop in dc.resources and friends; we
			# want to keep some stuff there so we can push out deleted
			# records.
			for tableName in ["dc.tablemeta", "tap_schema.tables",
					"tap_schema.columns", "tap_schema.keys", "tap_schema.key_columns",
					"dc.res_dependencies", "ivoa._obscoresources",
					"dc.biblinks", "dc.rdmeta"]:
				if base.UnmanagedQuerier(connection
						).getTableType(tableName) is not None:
					connection.execute(
						"delete from %s where sourceRd=%%(sourceRD)s"%tableName,
						{"sourceRD": rdId})

		try:
			with connection.savepoint():
				connection.execute("drop schema %s"%rd.schema)
		except Exception as msg:
			api.ui.notifyWarning("Cannot drop RD %s's schema %s because:"
				" %s."%(rd.sourceId, rd.schema, utils.safe_str(msg)))

	obscore.restoreObscore(connection)


def dropRD():
	"""parses the command line and drops data and services for the
	selected RD.

	This is a "toplevel" function intended to be called by cli directly.
	"""
	def parseCmdline():
		from argparse import ArgumentParser
		parser = ArgumentParser(
			description="Drops all tables made in an RD's data element.")
		parser.add_argument("rdid", help="RD path or id to drop")
		parser.add_argument("ddids", help="Optional dd id(s) if you"
			" do not want to drop the entire RD.  Note that no service"
			" publications will be undone if you give DD ids.", nargs="*")
		parser.add_argument("-s", "--system", help="drop tables even if they"
			" are system tables",
			dest="systemImport", action="store_true")
		parser.add_argument("-f", "--force", help="Even if the RD isn't"
			" found, try to purge entries referencing it. This only"
			" makes sense with the full RD id.",
			dest="force", action="store_true")
		parser.add_argument("--all", help="drop all DDs in the RD,"
			" not only the auto ones (overrides manual selection)",
			dest="dropAll", action="store_true")
		return parser.parse_args()

	opts = parseCmdline()
	rdId = opts.rdid
	ddIds = None
	if opts.ddids:
		ddIds = set(opts.ddids)

	conn = base.getDBConnection("feed")
	_do_dropRD(opts, rdId, conn, ddIds)
	conn.commit()
