/* 
 *                                dbMango
 *
 * Copyright 2025 Deutsche Bank AG
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
﻿using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using Rms.Risk.Mango.Interfaces;
using Rms.Service.Bootstrap.Security;

namespace Rms.Risk.Mango.Services.Context;

// ReSharper disable InconsistentNaming
public class DatabaseConfigurationService : IDatabaseConfigurationService
// ReSharper restore InconsistentNaming
{
    public static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);

    private readonly IDatabaseConfigurationStorage? _storage;
    private readonly IOptions<DatabasesConfig>      _config;
    private readonly IPasswordManager               _passwordManager;

    public const string ContextTypeNameDbMangoDatabase    = "dbMangoDatabase";

    public DatabaseConfigurationService(
        IOptions<DatabasesConfig>      config,
        IPasswordManager               passwordManager,
        IDatabaseConfigurationStorage? storage = null
        )
    {
        _storage         = storage;
        _config          = config;
        _passwordManager = passwordManager;

        Databases = _config.Value.Databases.ToDictionary(x => x.Key, x => x.Value.Clone());

        var cts    = new CancellationTokenSource(DefaultTimeout);
        _ = Task.Run(Reload, cts.Token);
    }

    private ConcurrentDictionary<string,DbMangoDatabaseConfigContext> RawDatabases { get; set; } = [];
    public  Dictionary<string, DatabasesConfig.DatabaseConfig>        Databases    { get; set; }

    public async Task Reload()
    {
        if ( _storage == null)
            return;

        var cts    = new CancellationTokenSource(DefaultTimeout);
        var raw = await _storage.List("dbMango", cts.Token);
        RawDatabases = new(raw.ToDictionary(x => x.Name, x => x));

        var newDb = _config.Value.Databases.ToDictionary(x => x.Key, x => x.Value.Clone());

        foreach (var databaseConfig in RawDatabases.Select( x => (x.Key, Config : ConvertTo(x.Value))))
        {
            newDb[databaseConfig.Key] =  databaseConfig.Config;
        }

        Databases = newDb;
    }

    public async Task Update(string name, DatabasesConfig.DatabaseConfig c, string email)
    {
        await Reload();

        if (RawDatabases.ContainsKey(name))
            await UpdateInternal(name, c, email);
        else
            await CreateInternal(name, c, email);
    }

    private async Task CreateInternal(string name, DatabasesConfig.DatabaseConfig c, string email)
    {
        var ctx = ConvertFrom(name, c, true);

        if ( _storage != null )
        {
            var cts    = new CancellationTokenSource(DefaultTimeout);
            await _storage.Create(ctx, email, cts.Token);
        }

        RawDatabases[name] = ctx;
    }

    private async Task UpdateInternal(string name, DatabasesConfig.DatabaseConfig c, string email)
    {

        var ctx = ConvertFrom(name, c, false);

        if ( _storage != null )
        {
            var cts    = new CancellationTokenSource(DefaultTimeout);
            await _storage.Update(ctx, email, cts.Token);
        }

        RawDatabases[name] = ctx;
    }

    public async Task Delete(string name, string email)
    {
        await Reload();

        if (!RawDatabases.TryGetValue(name, out var raw))
            throw new ApplicationException($"Configuration for {name} is not loaded");

        if ( _storage != null )
        {
            var cts    = new CancellationTokenSource(DefaultTimeout);
            await _storage.Delete(raw.ID, email, cts.Token);
        }

        RawDatabases.Remove(name, out _);
    }


    private DatabasesConfig.DatabaseConfig ConvertTo(DbMangoDatabaseConfigContext ctx)
    {
        var userPass  = DecryptPassword(ctx.DatabaseParams.UserAuthPassword);
        var adminPass = DecryptPassword(ctx.DatabaseParams.AdminAuthPassword);

        var c = new DatabasesConfig.DatabaseConfig
        {
            Contacts = ctx.DatabaseParams.Contacts,
            Config = new()
            {
                MongoDbUrl       = ctx.DatabaseParams.MongoDbUrl,
                MongoDbDatabase  = ctx.DatabaseParams.MongoDbDatabase,
                DirectConnection = ctx.DatabaseParams.DirectConnection,
                UseTls           = ctx.DatabaseParams.UseTls,
                AllowShardAccess = ctx.DatabaseParams.AllowShardAccess,
                Auth = new()
                {
                    User         = ctx.DatabaseParams.UserAuthUser,
                    Password     = userPass,
                    AuthDatabase = ctx.DatabaseParams.UserAuthAuthDatabase,
                    Method       = ctx.DatabaseParams.UserAuthMethod
                },
                AdminAuth = new()
                {
                    User         = ctx.DatabaseParams.AdminAuthUser,
                    Password     = adminPass,
                    AuthDatabase = ctx.DatabaseParams.AdminAuthAuthDatabase,
                    Method       = ctx.DatabaseParams.AdminAuthMethod
                }
            },
            Groups = ctx.LdapParams
        };

        if (string.IsNullOrWhiteSpace(c.Config.Auth.User) ||
            string.IsNullOrWhiteSpace(c.Config.Auth.Password))
        {
            c.Config.Auth = null;
        }

        if (string.IsNullOrWhiteSpace(c.Config.AdminAuth.User) ||
            string.IsNullOrWhiteSpace(c.Config.AdminAuth.Password))
        {
            c.Config.AdminAuth = null;
        }

        return c;
    }

    private string DecryptPassword(string? p) =>
        string.IsNullOrWhiteSpace(p)
            ? ""
            : p.StartsWith("*")
                ? _passwordManager.DecryptPassword(p)
                : p;

    private string EncryptPassword(string? p) =>
        string.IsNullOrWhiteSpace(p)
            ? ""
            : p.StartsWith("*")
                ? p
                : _passwordManager.EncryptPassword(p);

    private DbMangoDatabaseConfigContext ConvertFrom( string name, DatabasesConfig.DatabaseConfig c, bool isNew )
    {
        DbMangoDatabaseConfigContext? raw = null;
        if (!isNew && !RawDatabases.TryGetValue(name, out raw))
            throw new ApplicationException($"Configuration for {name} is not loaded");

        var userPass  = EncryptPassword(c.Config.Auth?.Password);
        var adminPass = EncryptPassword(c.Config.AdminAuth?.Password);

        var ctx = new DbMangoDatabaseConfigContext
        {
            Type                = ContextTypeNameDbMangoDatabase ,
            Name                = isNew ? name : raw!.Name,
            ID                  = isNew ? 0 : raw!.ID,
            IsTemplate          = !isNew && raw!.IsTemplate,
            ProposedForDeletion = !isNew && raw!.ProposedForDeletion,

            DatabaseParams = new()
            {
                Contacts              = c.Contacts,
                MongoDbUrl            = c.Config.MongoDbUrl,
                MongoDbDatabase       = c.Config.MongoDbDatabase,
                DirectConnection      = c.Config.DirectConnection,
                UseTls                = c.Config.UseTls,
                AllowShardAccess      = c.Config.AllowShardAccess,

                UserAuthUser          = c.Config.Auth?.User ?? "",
                UserAuthPassword      = userPass,
                UserAuthAuthDatabase  = c.Config.Auth?.AuthDatabase,
                UserAuthMethod        = c.Config.Auth?.Method,
                
                AdminAuthUser         = c.Config.AdminAuth?.User ?? "",
                AdminAuthPassword     = adminPass,
                AdminAuthAuthDatabase = c.Config.AdminAuth?.AuthDatabase,
                AdminAuthMethod       = c.Config.AdminAuth?.Method,
            },
            LdapParams = c.Groups
        };

        return ctx;
    }

}