#!/usr/bin/env python3

# This script is intended to be called automatically every day (e.g. via cron).
# It only output stuff if new ranks have to be inserted. Therefore the output
# may be redirected to email notifying about manual action to transfer the
# ranks to MySQL.
#
# Configure cron as the user running the DDNet-Server processes
#
#     $ crontab -e
#     30 5 * * * /path/to/this/script/move_sqlite.py --from /path/to/ddnet-server.sqlite
#
# Afterwards configure a MTA (e.g. postfix) and the users email address.

import sqlite3
import argparse
from time import strftime
import os
from datetime import datetime, timedelta

TABLES = ["record_race", "record_teamrace", "record_saves"]


def sqlite_table_exists(cursor, table):
	cursor.execute(f"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='{table}'")
	return cursor.fetchone()[0] != 0


def sqlite_num_transfer(conn, table, date):
	c = conn.cursor()
	if not sqlite_table_exists(c, table):
		return 0
	query = f"SELECT COUNT(*) FROM {table}"
	if date is not None:
		query += f' WHERE Timestamp < DATETIME("{date}", "utc")'
	c.execute(query)
	num = c.fetchone()[0]
	return num


def transfer(file_from, file_to, date, keep_timestamp_utc):
	conn_to = sqlite3.connect(file_to, isolation_level="EXCLUSIVE")
	cursor_to = conn_to.cursor()

	conn_from = sqlite3.connect(file_from, isolation_level="EXCLUSIVE")
	conn_from.text_factory = lambda b: b.decode(errors="ignore").rstrip()
	for line in conn_from.iterdump():
		cursor_to.execute(line)
	for table in TABLES:
		cursor_to.execute(f'INSERT INTO {table} SELECT * FROM {table}_backup WHERE Timestamp < DATETIME("{date}", "utc")')
	cursor_to.close()
	conn_to.commit()

	cursor_from = conn_from.cursor()
	for table in TABLES:
		if sqlite_table_exists(cursor_from, table):
			cursor_from.execute(f"DELETE FROM {table}")
		backup_table = f"{table}_backup"
		if sqlite_table_exists(cursor_from, backup_table):
			cursor_from.execute(f'DELETE FROM {backup_table} WHERE Timestamp < DATETIME("{date}", "utc")')
	cursor_from.close()
	conn_from.commit()
	conn_from.close()

	cursor_to = conn_to.cursor()
	# delete non-moved backup-rows:
	for table in TABLES:
		backup_table = f"{table}_backup"
		if sqlite_table_exists(cursor_to, backup_table):
			cursor_to.execute(f"DELETE FROM {backup_table}")

	if not keep_timestamp_utc:
		# change date from utc to wanted current timezone for mysql https://github.com/ddnet/ddnet/issues/6105
		for table in TABLES:
			cursor_to.execute(f"""
				UPDATE {table}
				SET Timestamp = DATETIME(original.Timestamp, "localtime")
				FROM (
					SELECT rowid, Timestamp FROM {table}
				) as original
				WHERE {table}.rowid = original.rowid""")

	cursor_to.close()
	conn_to.commit()

	for line in conn_to.iterdump():
		print(line.encode("utf-8"))
	conn_to.close()


def main():
	default_output = "ddnet-server-" + strftime("%Y-%m-%dT%H:%M:%S") + ".sqlite"
	parser = argparse.ArgumentParser(description="Move DDNet ranks, teamranks and saves from a possible active SQLite3 to a new one", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
	parser.add_argument("--from", "-f", dest="f", default="ddnet-server.sqlite", help="Input file where ranks are deleted from when moved successfully (default: ddnet-server.sqlite)")
	parser.add_argument("--to", "-t", default=default_output, help="Output file where ranks are saved adds current date by default")
	parser.add_argument("--backup-timeout", default=60, type=int, help="Time in minutes until when a rank is moved from the _backup tables")
	parser.add_argument("--keep-timestamp-utc", default=False, action="store_true", help="Timestamps are converted to localtime by default. To keep them utc set this config option")
	args = parser.parse_args()

	if not os.path.exists(args.f):
		print(f"Warning: '{args.f}' does not exist (yet). Is the path specified correctly?")
		return

	date = (datetime.now() - timedelta(minutes=args.backup_timeout)).strftime("%Y-%m-%d %H:%M:%S")

	conn = sqlite3.connect(args.f)
	num = {}
	for table in TABLES:
		num[table] = sqlite_num_transfer(conn, table, None)
		num[table] += sqlite_num_transfer(conn, f"{table}_backup", date)
	conn.close()
	if sum(num.values()) == 0:
		return

	print(f"{num} new entries in backup database found ({num['record_race']} ranks, {num['record_teamrace']} teamranks, {num['record_saves']} saves)")
	print(f"Moving entries from {os.path.abspath(args.f)} to {os.path.abspath(args.to)}")
	print("You can use the following commands to import the entries to MySQL (using https://github.com/techouse/sqlite3-to-mysql/):")
	print()
	print(f"sqlite3mysql --sqlite-file {os.path.abspath(args.to)} --ignore-duplicate-keys --mysql-insert-method IGNORE --sqlite-tables record_race record_teamrace record_saves --mysql-password 'PW2' --mysql-host 'host' --mysql-database teeworlds --mysql-user teeworlds")
	print(f"When the ranks are transferred successfully to mysql, {os.path.abspath(args.to)} can be removed")
	print()
	print("Log of the transfer:")
	print()

	transfer(args.f, args.to, date, args.keep_timestamp_utc)


if __name__ == "__main__":
	main()
