using Microsoft.Data.Sqlite;
using System;

namespace Elwig.Helpers {
    public static class AppDbUpdater {

        public static readonly int RequiredSchemaVersion = 2;

        private static int _versionOffset = 0;
        private static readonly Action<SqliteConnection>[] _updaters = new[] {
            UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3
        };

        private static void ExecuteNonQuery(SqliteConnection cnx, string sql) {
            using var cmd = cnx.CreateCommand();
            cmd.CommandText = sql;
            cmd.ExecuteNonQuery();
        }

        private static object? ExecuteScalar(SqliteConnection cnx, string sql) {
            using var cmd = cnx.CreateCommand();
            cmd.CommandText = sql;
            return cmd.ExecuteScalar();
        }

        public static string CheckDb() {
            using var cnx = AppDbContext.Connect();

            var applId = (long?)ExecuteScalar(cnx, "PRAGMA application_id") ?? 0;
            if (applId != 0x454C5747) throw new Exception("Invalid application_id of database");

            var schemaVers = (long?)ExecuteScalar(cnx, "PRAGMA schema_version") ?? 0;
            _versionOffset = (int)(schemaVers % 100);
            if (_versionOffset != 0) {
                // schema was modified manually/externally
                // TODO issue warning
            }
            UpdateDbSchema(cnx, (int)(schemaVers / 100), RequiredSchemaVersion);

            var userVers = (long?)ExecuteScalar(cnx, "PRAGMA user_version") ?? 0;
            var major = userVers >> 24;
            var minor = (userVers >> 16) & 0xFF;
            var patch = userVers & 0xFFFF;

            if (App.VersionMajor > major ||
                (App.VersionMajor == major && App.VersionMinor > minor) ||
                (App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch))
            {
                long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch;
                ExecuteNonQuery(cnx, $"PRAGMA user_version = {vers}");
            }

            return $"{major}.{minor}.{patch}";
        }

        private static void UpdateDbSchema(SqliteConnection cnx, int fromVersion, int toVersion) {
            if (fromVersion == toVersion) {
                return;
            } else if (fromVersion > toVersion) {
                throw new Exception("schema_version of database is too new");
            } else if (toVersion - 1 > _updaters.Length) {
                throw new Exception("Unable to update database schema: Updater not implemented");
            } else if (fromVersion <= 0) {
                throw new Exception("schema_version of database is invalid");
            }

            ExecuteNonQuery(cnx, "PRAGMA locking_mode = EXCLUSIVE");
            ExecuteNonQuery(cnx, "BEGIN EXCLUSIVE");
            for (int i = fromVersion; i < toVersion; i++) {
                _updaters[i - 1](cnx);
            }
            ExecuteNonQuery(cnx, "COMMIT");
            ExecuteNonQuery(cnx, "VACUUM");
            ExecuteNonQuery(cnx, $"PRAGMA schema_version = {toVersion * 100 + _versionOffset}");
        }

        private static void UpdateDbSchema_1_To_2(SqliteConnection cnx) {
            ExecuteNonQuery(cnx, "DROP VIEW v_area_commitment");
            ExecuteNonQuery(cnx, "ALTER TABLE delivery_part DROP COLUMN weighing_reason");
            ExecuteNonQuery(cnx, "ALTER TABLE delivery_part ADD COLUMN weighing_reason TEXT CHECK(NOT (manual_weighing = FALSE AND weighing_reason IS NOT NULL))");
        }

        private static void UpdateDbSchema_2_To_3(SqliteConnection cnx) { }
    }
}