using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core.Extensions;
using e_suite.Database.Core.Tables.UserManager;
using e_suite.Utilities.Pagination;
using eSuite.Core.Miscellaneous;
using System.Linq.Expressions;

namespace e_suite.Modules.SSOManager;

public class SsoManager : ISsoManager
{
    private readonly ISsoManagerRepository _ssoManagerRepository;
    private readonly IPatchFactory _patchFactory;

    public SsoManager(ISsoManagerRepository ssoManagerRepository, IPatchFactory patchFactory)
    {
        _ssoManagerRepository = ssoManagerRepository;
        _patchFactory = patchFactory;
    }

    public async Task<IPaginatedData<ReadSsoProvider>> GetSsoProvidersAsync(Paging paging, CancellationToken cancellationToken)
    {
        var ssoProviders = _ssoManagerRepository.GetSsoProviders().Where(x => x.Deleted == false);

        var paginatedData = await PaginatedData.Paginate<SsoProvider, ReadSsoProvider>(ssoProviders, paging,
            KeySelector, cancellationToken);
        
        return paginatedData;
    }

    public async Task<ReadSsoProvider> GetSsoProviderAsync( IGeneralIdRef generalIdRef, CancellationToken cancellationToken )
    {
        var ssoProvider = await _ssoManagerRepository.GetSsoProviders().FindByGeneralIdRefAsync(generalIdRef, cancellationToken) 
                          ?? throw new NotFoundException("Unable to find SsoProvider");
        return new ReadSsoProvider(ssoProvider);
    }
    
    private Expression<Func<SsoProvider, object>> KeySelector(string? sortKey)
    {
        return sortKey?.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            "name" => x => x.Name,
            _ => x => x.Name
        };
    }

    public async Task AddSsoProviderAsync(
        AuditUserDetails auditUserDetails,
        CreateSsoProvider ssoProvider,
        CancellationToken cancellationToken
    )
    {
        await _ssoManagerRepository.TransactionAsync(async () =>
        {
            if (ssoProvider.Guid != null && await _ssoManagerRepository.GetSsoProviderAsync( new GeneralIdRef {Guid = ssoProvider.Guid}, cancellationToken) != null)
                throw new ExistsException("An SsoProvider with this guid exists");

            var newSsoProvider = new SsoProvider
            {
                Name = ssoProvider.Name,
                Guid = ssoProvider.Guid ?? Guid.NewGuid(),
                ClientId = ssoProvider.ClientId,
                ClientSecret = ssoProvider.ClientSecret,
                ValidIssuer = ssoProvider.ValidIssuer,
                AuthorizationEndpoint = ssoProvider.AuthorizationEndpoint,
                TokenEndpoint = ssoProvider.TokenEndpoint,
                IsPublic = ssoProvider.IsPublic,
                Deleted = false
            };

            await _ssoManagerRepository.CreateNewSsoProviderAsync(auditUserDetails, newSsoProvider, cancellationToken);
        });
    }

    public async Task EditSsoProviderAsync(
        AuditUserDetails auditUserDetails,
        EditSsoProvider editSsoProvider,
        CancellationToken cancellationToken
    )
    {
        await AlterSsoProviderAsync(auditUserDetails, editSsoProvider.Id, ssoProvider =>
        {
            ssoProvider.AuthorizationEndpoint = editSsoProvider.AuthorizationEndpoint;
            ssoProvider.Name = editSsoProvider.Name;
            ssoProvider.ClientId = editSsoProvider.ClientId;
            ssoProvider.ClientSecret = editSsoProvider.ClientSecret;
            ssoProvider.ValidIssuer = editSsoProvider.ValidIssuer;
            ssoProvider.AuthorizationEndpoint = editSsoProvider.AuthorizationEndpoint;
            ssoProvider.TokenEndpoint = editSsoProvider.TokenEndpoint;
            ssoProvider.IsPublic = editSsoProvider.IsPublic;
            ssoProvider.Deleted = false;
            return Task.CompletedTask;
        },
        cancellationToken );
    }

    public async Task PatchSsoProviderAsync(
        AuditUserDetails auditUserDetails,
        GeneralIdRef generalIdRef,
        PatchSsoProvider patchSsoProvider,
        CancellationToken cancellationToken
    )
    {
        var patch = _patchFactory.Create(patchSsoProvider);
        await AlterSsoProviderAsync(auditUserDetails, generalIdRef, async ssoProvider =>
            {
                await patch.ApplyTo(ssoProvider);
            },
            cancellationToken);
    }

    private async Task AlterSsoProviderAsync(
        AuditUserDetails auditUserDetails,
        IGeneralIdRef generalIdRef,
        Func<SsoProvider, Task> applyChanges,
        CancellationToken cancellationToken
    )
    {
        await _ssoManagerRepository.TransactionAsync(async () =>
        {
            var ssoProvider = await _ssoManagerRepository.GetSsoProviderAsync(generalIdRef, cancellationToken);
            if (ssoProvider is null)
                throw new NotFoundException("SsoProvider with this id does not exists");

            await applyChanges(ssoProvider);

            await _ssoManagerRepository.EditNewSsoProviderAsync(auditUserDetails, ssoProvider, cancellationToken);
        });
    }

    public async Task RemoveSsoProviderAsync(AuditUserDetails auditUserDetails, IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        await AlterSsoProviderAsync(auditUserDetails, generalIdRef, async ssoProvider =>
            {
                ssoProvider.Deleted = true;
            },
            cancellationToken);
    }
}