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

public class MigrationService : IHostedService
{
    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;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await MigrateAsync();
    }

    public async Task StopAsync(CancellationToken cancellationToken) { }
    
    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; }
                Task<int>? invocation = (Task<int>?)method.Invoke(null, [_ldapService]) ?? throw new Exception("Invocation is null");
                version = await invocation;
                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;
    }
    public static async Task<int> UpdateFrom1Async(LdapService ldapService)
    {
        // Add the description attribute to ou=assets
        AssetsMetadataModel assetsMetadataModel = new() { CnCounter = 1 };
        LdapAttribute ldapAttribute = new("description", JsonSerializer.Serialize(assetsMetadataModel));
        LdapModification ldapModification = new(LdapModification.Add, ldapAttribute);
        await ldapService.ModifyAsync(ldapService.AssetsBaseDn, ldapModification);
        return 2;
    }

    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;
        }
    }

}