using Berufsschule_HAM.Models;
using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;
using System.Reflection;
namespace Berufsschule_HAM.Services;

public class MigrationService
{
    private readonly LdapService _ldapService;
    private readonly ILogger<MigrationService> _logger;
    private readonly LdapConfig _ldapConfig;

    public MigrationService(LdapService ldapService, ILogger<MigrationService> logger, IOptions<LdapConfig> ldapConfig)
    {
        _ldapService = ldapService;
        _logger = logger;
        _ldapConfig = ldapConfig.Value;
        MigrateAsync().ConfigureAwait(false);
    }
    public async Task<MigrationModel> MigrateAsync()
    {
        MigrationModel migrationModel = await _ldapService.GetMigrationVersionAsync();
        int version = migrationModel.Version;
        string dc = _ldapConfig.BaseDn.Split("=")[1];

        List<MethodInfo> updateMethods = [.. typeof(MigrationService)
            .GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Where(m => m.Name.StartsWith("UpdateFrom")
                && m.Name.EndsWith("Async")
                && m.ReturnType == typeof(Task<int>))
            .OrderBy(m => int.Parse(m.Name["UpdateFrom".Length..^5]))]; // TODO remove magic number ("5" -> global constant)

        foreach (var method in updateMethods)
        {
            int methodVersion = int.Parse(method.Name["UpdateFrom".Length..^5]); // TODO TODO remove magic number ("5" -> global constant)
            if (methodVersion >= version)
            {
                _logger.LogInformation("Migrating LDAP database from version {version} to {methodVersion}", [version, methodVersion + 1]);
                if (method is null) { continue; }
#pragma warning disable CS8605 // Unboxing a possibly null value.
                version = (int)method.Invoke(null, [_ldapService]);
#pragma warning restore CS8605 // Unboxing a possibly null value.
            }
        }

        if (version != migrationModel.Version)
        {
            migrationModel.Version = version;
            await _ldapService.UpdateMigrationVersionAsync(migrationModel);
        }

        return migrationModel;
    }

    public static async Task<int> UpdateFrom0Async(LdapService ldapService)
    {
        LdapAttributeSet usersAttributes =
        [
            new("objectClass", "organizationalUnit"),
            new("objectClass", "top"),
            new("ou", "users")
        ];
        await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.UsersBaseDn, usersAttributes);
        LdapAttributeSet locationsAttributes =
        [
            new("objectClass", "organizationalUnit"),
            new("objectClass", "top"),
            new("ou", "locations")
        ];
        await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.LocationsBaseDn, locationsAttributes);
        LdapAttributeSet groupsAttributes =
        [
            new("objectClass", "organizationalUnit"),
            new("objectClass", "top"),
            new("ou", "groups")
        ];
        await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.GroupsBaseDn, groupsAttributes);
        LdapAttributeSet assetsAttributes =
        [
            new("objectClass", "organizationalUnit"),
            new("objectClass", "top"),
            new("ou", "assets")
        ];
        await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.AssetsBaseDn, assetsAttributes);
        LdapAttributeSet migrationsAttributes =
        [
            new("objectClass", "organizationalUnit"),
            new("objectClass", "top"),
            new("ou", "migrations"),
            new("description", "{\"Version\": 1}")
        ];
        await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.MigrationsBaseDn, migrationsAttributes);
        return 1;
    }

    private static async Task TryCreateObjectIgnoreAlreadyExists(LdapService ldapService, string dn, LdapAttributeSet ldapAttributes)
    {
        try
        {
            await ldapService.CreateObject(dn, ldapAttributes);
        }
        catch (LdapException ex)
        {
            if (ex.ResultCode != LdapException.EntryAlreadyExists)
            {
                throw;
            }
        }
        catch (Exception)
        {
            throw;
        }
    }

}