using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.Web.Script.Serialization;
using mojoPortal.Business;
using mojoPortal.Business.WebHelpers;
using Mono.Data.Sqlite;

namespace mojoPortal.Web.Admin;

public class MigrationAnalyzer : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.Expires = -1;
        context.Response.ContentType = "application/json";

        try
        {
            if (!IsAuthorized(context))
            {
                WriteJson(context, new { success = false, error = "Access denied" });
                return;
            }

            string requestBody = new StreamReader(context.Request.InputStream).ReadToEnd();
            Dictionary<string, object> jsonData = null;

            if (!string.IsNullOrEmpty(requestBody))
            {
                try
                {
                    jsonData = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(requestBody);
                }
                catch { }
            }

            string action = jsonData != null && jsonData.ContainsKey("action")
                ? Convert.ToString(jsonData["action"])
                : context.Request.Form["action"] ?? context.Request.QueryString["action"] ?? "status";

            switch ((action ?? string.Empty).ToLowerInvariant())
            {
                case "status":
                    WriteStatus(context);
                    break;
                case "analyze":
                case "analyzesource":
                    AnalyzeSource(context, jsonData);
                    break;
                case "migrate":
                case "siphon":
                    if (!IsPost(context))
                    {
                        WriteMethodNotAllowed(context);
                        return;
                    }

                    SiphonSource(context, jsonData);
                    break;
                case "resetempty":
                case "empty":
                    if (!IsPost(context))
                    {
                        WriteMethodNotAllowed(context);
                        return;
                    }

                    ResetToEmpty(context);
                    break;
                default:
                    WriteJson(context, new { success = false, error = "Unknown action" });
                    break;
            }
        }
        catch (Exception ex)
        {
            WriteJson(context, new { success = false, error = ex.Message, detail = ex.ToString() });
        }
    }

    private bool IsAuthorized(HttpContext context)
    {
        if (!context.Request.IsAuthenticated || !WebUser.IsAdmin)
        {
            context.Response.StatusCode = 403;
            return false;
        }

        SiteSettings siteSettings = CacheHelper.GetCurrentSiteSettings();
        if (siteSettings == null || !siteSettings.IsServerAdminSite)
        {
            context.Response.StatusCode = 403;
            return false;
        }

        return true;
    }

    private bool IsPost(HttpContext context)
    {
        return string.Equals(context.Request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase);
    }

    private void WriteMethodNotAllowed(HttpContext context)
    {
        context.Response.StatusCode = 405;
        WriteJson(context, new { success = false, error = "Method not allowed" });
    }

    private void WriteStatus(HttpContext context)
    {
        string dbFolder = GetDbFolder();
        string currentDbPath = GetCurrentDbPath();
        string emptyDbPath = GetEmptyDbPath();
        string legacyDbPath = ResolveSourcePath("legacy.db.config");

        var currentStats = SafeGetStats(currentDbPath);
        var emptyStats = SafeGetStats(emptyDbPath);
        var legacyStats = SafeGetStats(legacyDbPath);

        var sources = GetAvailableSourceFiles()
            .Select(path => new
            {
                fileName = Path.GetFileName(path),
                stats = SafeGetStats(path)
            })
            .ToArray();

        WriteJson(context, new
        {
            success = true,
            dbFolder,
            state = DetermineState(currentStats, emptyStats),
            currentDb = new { fileName = Path.GetFileName(currentDbPath), exists = File.Exists(currentDbPath), stats = currentStats },
            emptyDb = new { fileName = Path.GetFileName(emptyDbPath), exists = File.Exists(emptyDbPath), stats = emptyStats },
            legacyDb = new { fileName = Path.GetFileName(legacyDbPath), exists = File.Exists(legacyDbPath), stats = legacyStats },
            sources
        });
    }

    private void AnalyzeSource(HttpContext context, Dictionary<string, object> jsonData)
    {
        string sourceDbPath = ResolveRequestedSource(jsonData);

        if (!File.Exists(sourceDbPath))
        {
            WriteJson(context, new { success = false, error = "Database files not found" });
            return;
        }

        WriteJson(context, new
        {
            success = true,
            sourceFile = Path.GetFileName(sourceDbPath),
            pages = SafeGetStat(sourceDbPath, "mp_Pages"),
            modules = SafeGetStat(sourceDbPath, "mp_Modules"),
            moduleSettings = SafeGetStat(sourceDbPath, "mp_ModuleSettings"),
            siteSettings = SafeGetStat(sourceDbPath, "mp_SiteSettings"),
            features = SafeGetDistinctFeatureCount(sourceDbPath),
            tables = SafeGetTables(sourceDbPath)
        });
    }

    private void ResetToEmpty(HttpContext context)
    {
        string currentDbPath = GetCurrentDbPath();
        string emptyDbPath = GetEmptyDbPath();

        if (!File.Exists(emptyDbPath) || !File.Exists(currentDbPath))
        {
            WriteJson(context, new { success = false, error = "empty.db.config or mojo.db.config is missing" });
            return;
        }

        string backupPath = Path.Combine(GetDbFolder(), "mojo.backup-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + ".db.config");
        File.Copy(currentDbPath, backupPath, true);
        File.Copy(emptyDbPath, currentDbPath, true);

        WriteJson(context, new { success = true, state = "Empty", backupFile = Path.GetFileName(backupPath), message = "mojo.db.config was reset from empty.db.config" });
    }

    private void SiphonSource(HttpContext context, Dictionary<string, object> jsonData)
    {
        string currentDbPath = GetCurrentDbPath();
        string sourceDbPath = ResolveRequestedSource(jsonData);
        int siteId = jsonData != null && jsonData.ContainsKey("siteId") ? Convert.ToInt32(jsonData["siteId"]) : 1;

        if (!File.Exists(sourceDbPath) || !File.Exists(currentDbPath))
        {
            WriteJson(context, new { success = false, error = "Database files not found" });
            return;
        }

        object engine = CreateMigrationEngine(sourceDbPath, currentDbPath);
        if (engine == null)
        {
            WriteJson(context, new { success = false, error = "Could not load LegacyDataMigrationEngine from deployed data assembly" });
            return;
        }

        var pageIdMap = new Dictionary<int, int>();
        int pagesCount = InvokeEngineInt(engine, "MigratePages", siteId, pageIdMap);
        int modulesCount = InvokeEngineInt(engine, "MigrateModules", siteId, pageIdMap);
        int settingsCount = InvokeEngineInt(engine, "MigrateSiteSettings", siteId);
        var logs = GetEngineLogs(engine);

        WriteJson(context, new { success = true, state = "Filled", sourceFile = Path.GetFileName(sourceDbPath), pages = pagesCount, modules = modulesCount, siteSettings = settingsCount, logs = logs });
    }

    private object CreateMigrationEngine(string sourceDbPath, string currentDbPath)
    {
        string[] typeNames =
        {
            "mojoPortal.Data.Migration.LegacyDataMigrationEngine, mojoPortal.Data",
            "mojoPortal.Data.Migration.LegacyDataMigrationEngine, mojoPortal.Data.SQLite"
        };

        foreach (string typeName in typeNames)
        {
            Type engineType = Type.GetType(typeName, false);
            if (engineType != null)
            {
                return Activator.CreateInstance(engineType, sourceDbPath, currentDbPath);
            }
        }

        return null;
    }

    private int InvokeEngineInt(object engine, string methodName, params object[] args)
    {
        var methods = engine.GetType().GetMethods()
            .Where(m => string.Equals(m.Name, methodName, StringComparison.Ordinal))
            .ToArray();

        var method = methods.FirstOrDefault(m => ParametersMatch(m.GetParameters(), args));
        if (method == null) return 0;

        object value = method.Invoke(engine, args);
        return value != null ? Convert.ToInt32(value) : 0;
    }

    private bool ParametersMatch(System.Reflection.ParameterInfo[] parameters, object[] args)
    {
        if (parameters.Length != args.Length) return false;

        for (int i = 0; i < parameters.Length; i++)
        {
            object arg = args[i];
            Type parameterType = parameters[i].ParameterType;

            if (arg == null)
            {
                if (parameterType.IsValueType && Nullable.GetUnderlyingType(parameterType) == null)
                {
                    return false;
                }

                continue;
            }

            Type argType = arg.GetType();
            if (!parameterType.IsAssignableFrom(argType))
            {
                return false;
            }
        }

        return true;
    }

    private IEnumerable<string> GetEngineLogs(object engine)
    {
        var property = engine.GetType().GetProperty("MigrationLog");
        if (property == null) return Enumerable.Empty<string>();
        return property.GetValue(engine, null) as IEnumerable<string> ?? Enumerable.Empty<string>();
    }

    private string DetermineState(Dictionary<string, int> currentStats, Dictionary<string, int> emptyStats)
    {
        if (currentStats.Count == 0 || emptyStats.Count == 0) return "Unknown";
        bool sameAsEmpty = GetStatValue(currentStats, "Pages") == GetStatValue(emptyStats, "Pages")
            && GetStatValue(currentStats, "Modules") == GetStatValue(emptyStats, "Modules")
            && GetStatValue(currentStats, "ModuleSettings") == GetStatValue(emptyStats, "ModuleSettings");
        return sameAsEmpty ? "Empty" : "Filled";
    }

    private int GetStatValue(Dictionary<string, int> stats, string key)
    {
        int value;
        return stats != null && stats.TryGetValue(key, out value) ? value : 0;
    }

    private Dictionary<string, int> SafeGetStats(string dbPath)
    {
        try
        {
            if (!File.Exists(dbPath)) return new Dictionary<string, int>();
            return new Dictionary<string, int>
            {
                { "Pages", SafeGetStat(dbPath, "mp_Pages") },
                { "Modules", SafeGetStat(dbPath, "mp_Modules") },
                { "ModuleSettings", SafeGetStat(dbPath, "mp_ModuleSettings") },
                { "SiteSettings", SafeGetStat(dbPath, "mp_SiteSettings") },
                { "UniqueFeatures", SafeGetDistinctFeatureCount(dbPath) }
            };
        }
        catch
        {
            return new Dictionary<string, int>();
        }
    }

    private int SafeGetStat(string dbPath, string tableName)
    {
        try
        {
            using (var connection = new SqliteConnection("version=3,URI=file:" + dbPath))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT COUNT(*) FROM " + tableName;
                    return Convert.ToInt32(command.ExecuteScalar());
                }
            }
        }
        catch
        {
            return 0;
        }
    }

    private int SafeGetDistinctFeatureCount(string dbPath)
    {
        try
        {
            using (var connection = new SqliteConnection("version=3,URI=file:" + dbPath))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT COUNT(DISTINCT FeatureGuid) FROM mp_Modules";
                    return Convert.ToInt32(command.ExecuteScalar());
                }
            }
        }
        catch
        {
            return 0;
        }
    }

    private List<string> SafeGetTables(string dbPath)
    {
        var tables = new List<string>();
        try
        {
            using (var connection = new SqliteConnection("version=3,URI=file:" + dbPath))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name";
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            tables.Add(Convert.ToString(reader[0]));
                        }
                    }
                }
            }
        }
        catch { }
        return tables;
    }

    private string ResolveRequestedSource(Dictionary<string, object> jsonData)
    {
        if (jsonData != null && jsonData.ContainsKey("sourceFile")) return ResolveSourcePath(Convert.ToString(jsonData["sourceFile"]));
        if (jsonData != null && jsonData.ContainsKey("sourcePath"))
        {
            string sourcePath = Convert.ToString(jsonData["sourcePath"]);
            if (Path.IsPathRooted(sourcePath)) return sourcePath;
        }
        return ResolveSourcePath("legacy.db.config");
    }

    private IEnumerable<string> GetAvailableSourceFiles()
    {
        string folder = GetDbFolder();
        if (!Directory.Exists(folder)) return Enumerable.Empty<string>();
        return Directory.GetFiles(folder, "*.db.config", SearchOption.TopDirectoryOnly)
            .Where(path => !string.Equals(Path.GetFileName(path), "mojo.db.config", StringComparison.OrdinalIgnoreCase))
            .Where(path => !string.Equals(Path.GetFileName(path), "empty.db.config", StringComparison.OrdinalIgnoreCase))
            .OrderBy(path => Path.GetFileName(path));
    }

    private string ResolveSourcePath(string fileName) => Path.Combine(GetDbFolder(), Path.GetFileName(fileName ?? string.Empty));
    private string GetDbFolder() => HostingEnvironment.MapPath("~/Data/sqlitedb");
    private string GetCurrentDbPath() => HostingEnvironment.MapPath("~/Data/sqlitedb/mojo.db.config");
    private string GetEmptyDbPath() => HostingEnvironment.MapPath("~/Data/sqlitedb/empty.db.config");

    private void WriteJson(HttpContext context, object data)
    {
        var js = new JavaScriptSerializer();
        context.Response.Write(js.Serialize(data));
    }

    public bool IsReusable => false;
}
