using System.Diagnostics;
using DataModel.Database;
using DataModel.Database.Common;
using DataModel.MigrationScripts;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace DataModel;

public static class Setup
{
    private const string DB_READ_WRITE_MODE = "ReadWrite";
    private const string DB_READ_WRITE_CREATE_MODE = "ReadWriteCreate";

    private static string USED_DATA_FILE = string.Empty;
    public static SetupMaintenance SETUP_MAINTENANCE = new();

    public static string DataFile => Setup.USED_DATA_FILE;

    /// <summary>
    /// Tries to migrate the database.
    /// </summary>
    public static async Task PerformDataMigration(DataContext dbContext)
    {
        var pendingMigrations = (await dbContext.Database.GetPendingMigrationsAsync()).ToList();
        foreach (var pendingMigration in pendingMigrations)
        {
            Console.WriteLine($"The migration '{pendingMigration}' is pending.");
        }

        await dbContext.Database.MigrateAsync();
        
        //
        // Post migration actions:
        //
        if (pendingMigrations.Contains("20221106193544_202211AddUniqueIds"))
            await Script202211AddUniqueIds.PostMigrationAsync(dbContext);
    }

    /// <summary>
    /// Imports a JSON file into a new database.
    /// </summary>
    public static async Task ImportDataAndAddDatabase(this IServiceCollection serviceCollection, string path2JSONFile)
    {
        Console.WriteLine($"Importing the data from the JSON file '{path2JSONFile}' into a new database.");
        var tempPath = Path.GetTempFileName();

        Console.WriteLine($"The temporary database file is: {tempPath}");
        serviceCollection.AddDbContext<DataContext>(options => options.UseSqlite($"Filename={tempPath};Mode={DB_READ_WRITE_CREATE_MODE};"), ServiceLifetime.Transient);
        
        // Get the database service:
        await using var serviceProvider = serviceCollection.BuildServiceProvider();
        var dbContext = serviceProvider.GetRequiredService<DataContext>();

        Setup.USED_DATA_FILE = tempPath;
        Setup.SETUP_MAINTENANCE = new(tempPath, true);
        
        // Migrate the database to create the tables etc.:
        await Setup.PerformDataMigration(dbContext);
        
        // Next, we import the data from the provided JSON file:
        await dbContext.ImportAsync(path2JSONFile);

        //
        // Next, we enable the auto-export feature to keep the source file up to date.
        // The auto-export feature might exist, but we enforce it, when we work with a
        // temporary database source by a JSON file.
        //
        
        //
        // Enable the auto-export feature:
        //
        if (await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_ENABLED) is { } autoExportEnabled)
            autoExportEnabled.BoolValue = true;
        else
            dbContext.Settings.Add(new Setting {Code = SettingNames.AUTO_EXPORT_ENABLED, BoolValue = true});

        //
        // Set the auto-export path and file:
        //
        if(await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_DESTINATION_PATH) is { } autoExportPath)
            autoExportPath.TextValue = Path.GetDirectoryName(path2JSONFile) ?? string.Empty;
        else
            dbContext.Settings.Add(new Setting {Code = SettingNames.AUTO_EXPORT_DESTINATION_PATH, TextValue = Path.GetDirectoryName(path2JSONFile) ?? string.Empty});

        if(await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_FILENAME) is { } autoExportFile)
            autoExportFile.TextValue = Path.GetFileName(path2JSONFile);
        else
            dbContext.Settings.Add(new Setting {Code = SettingNames.AUTO_EXPORT_FILENAME, TextValue = Path.GetFileName(path2JSONFile)});

        // Ensure that the sensitive data setting is present and disabled by default:
        var _ = await dbContext.Settings.FirstOrDefaultAsync(n => n.Code == SettingNames.AUTO_EXPORT_SENSITIVE_DATA) ?? new Setting
        {
            Code = SettingNames.AUTO_EXPORT_SENSITIVE_DATA,
            BoolValue = false,
        };

        // Save the changes:
        await dbContext.SaveChangesAsync();
    }
    
    /// <summary>
    /// Creates and adds the database instance to the DI system (extension method).
    /// </summary>
    public static void AddDatabase(this IServiceCollection serviceCollection, string path2DataFile, bool createWhenNecessary = true)
    {
        Setup.USED_DATA_FILE = path2DataFile;
        Setup.SETUP_MAINTENANCE = new(path2DataFile, false);
        serviceCollection.AddDbContext<DataContext>(options => options.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};"), ServiceLifetime.Transient);
    }

    /// <summary>
    /// Create the database instance from the given path. Used for the EF tooling.
    /// </summary>
    public static DataContext CreateDatabaseInstance4Tooling(string path2DataFile, bool createWhenNecessary = true)
    {
        // Store the path to the database:
        Setup.USED_DATA_FILE = path2DataFile;
        Setup.SETUP_MAINTENANCE = new(path2DataFile, false);
        
        // Create a database builder:
        var builder = new DbContextOptionsBuilder<DataContext>();
        
        // Add the database configuration to the builder:
        builder.UseSqlite($"Filename={path2DataFile};Mode={(createWhenNecessary ? DB_READ_WRITE_CREATE_MODE : DB_READ_WRITE_MODE)};");
        
        // Next, construct the database context:
        var dbContext = new DataContext(builder.Options);
        return dbContext;
    }

    public readonly record struct SetupMaintenance(string PathToDataFile = "", bool RemoveTempDatabaseAfterwards = false) : IDisposable
    {
        public void Dispose()
        {
            if (!this.RemoveTempDatabaseAfterwards)
                return;

            Console.WriteLine("Removing the temporary database file...");
            try
            {
                var process = new Process
                {
                    StartInfo = new()
                    {
                        FileName = "cmd.exe",
                        Arguments = $"""/C del /Q /F "{Setup.SETUP_MAINTENANCE.PathToDataFile}" """,
                        UseShellExecute = false,
                        CreateNoWindow = true,
                    }
                };

                process.Start();
                Console.WriteLine($"The temporary database file '{this.PathToDataFile}' has been removed.");
            }
            catch(Exception e)
            {
                Console.WriteLine($"Failed to remove the temporary database file: {e.Message} // {e.InnerException?.Message}");
            }
        }
    }
}