using e_suite.API.Common.repository;
using e_suite.Database.Audit;
using e_suite.Database.Core;
using e_suite.Database.Core.Extensions;
using e_suite.Database.Core.Tables.Domain;
using e_suite.Database.Core.Tables.UserManager;
using eSuite.Core.Clock;
using eSuite.Core.Miscellaneous;
using Microsoft.EntityFrameworkCore;
using e_suite.Database.Core.Models;

namespace e_suite.Modules.UserManager.Repository;

public class UserManagerRepository : RepositoryBase, IUserManagerRepository
{
    private readonly IClock _clock;

    public UserManagerRepository(IClock clock, IEsuiteDatabaseDbContext databaseDbContext) : base(databaseDbContext)
    {
        _clock = clock;
    }

    public async Task<User?> GetUserByEmail(string loginEmail, CancellationToken cancellationToken)
    {
        var users = await GetUsers()
            .Include( x => x.SsoProvider)
            .Include( x => x.Domain)
            .Where(u => u.Email.Equals(loginEmail))
            .ToListAsync(cancellationToken);

        return users.FirstOrDefault();
    }

    public async Task<User?> GetUserSsoId(long ssoId, string ssoUserId, CancellationToken cancellationToken)
    {
        var users = await GetUsers()
            .Where(u => u.SsoProviderId.Equals(ssoId) && u.SsoSubject.Equals(ssoUserId))
            .ToListAsync(cancellationToken);
        return users.FirstOrDefault();
    }

    public async Task<User?> GetUserByDomainSsoId(long domainSsoId, string ssoUserId, CancellationToken cancellationToken)
    {
        var users = await GetUsers()
            .Where(u => u.Domain.SsoProviderId.Equals(domainSsoId) && u.SsoSubject.Equals(ssoUserId))
            .ToListAsync(cancellationToken);
        return users.FirstOrDefault();
    }


    public User? GetUserById(IGeneralIdRef generalIdRef)
    {
        var user = GetUsers().AsEnumerable().FindByGeneralIdRef(generalIdRef);
        if (user != null)
        {
            DatabaseDbContext.Entry(user).Reload(); // re-fetch from DB
        }

        return user;
    }

    public async Task<User?> GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
    {
        var user = await GetUsers().FindByGeneralIdRefAsync(generalIdRef, cancellationToken);
        if (user != null)
        {
            await DatabaseDbContext.Entry(user).ReloadAsync(cancellationToken); // re-fetch from DB
        }

        return user;
    }

    public async Task<SsoProvider?> GetSsoProviderById(long id, CancellationToken cancellationToken)
    {
        return await GetSsoProviders()
            .SingleOrDefaultAsync(x => x.Id.Equals(id), cancellationToken);
    }

    public IQueryable<SsoProvider> GetSsoProviders()
    {
        return DatabaseDbContext.SsoProviders;
    }

    public async Task SaveSingleUseGuidForUser(
        SingleUseGuid singleUseGuid,
        CancellationToken cancellationToken
    )
    {
        await DatabaseDbContext.SingleUseGuids.AddAsync(singleUseGuid, cancellationToken);
        await DatabaseDbContext.NoAuditSaveChangesAsync(cancellationToken);
    }

    public async Task<User?> GetUserBySingleUseGuid(Guid guid, CancellationToken cancellationToken)
    {
        var singleUseGuid = await DatabaseDbContext.SingleUseGuids
            .Where(x => x.Guid == guid && x.Expires >= _clock.GetNow)
            .Include( x => x.User)
            .ThenInclude( x => x.Domain)
            .FirstOrDefaultAsync(cancellationToken);

        return singleUseGuid?.User;
    }

    public async Task DeleteSingleUseGuid(Guid guid, CancellationToken cancellationToken)
    {
        var singleUseGuid = await DatabaseDbContext.SingleUseGuids
            .Where(x => x.Guid == guid)
            .FirstOrDefaultAsync(cancellationToken);

        if (singleUseGuid != null)
        {
            DatabaseDbContext.SingleUseGuids.Remove(singleUseGuid);
            await DatabaseDbContext.NoAuditSaveChangesAsync(cancellationToken);
        }
    }

    public async Task<Domain?> GetDomainById(GeneralIdRef domainId, CancellationToken cancellationToken)
    {
        return await DatabaseDbContext.Domains.FindByGeneralIdRefAsync(domainId, cancellationToken);
    }

    public async Task AddEmailUserAction(AuditUserDetails auditUserDetails, EmailUserAction emailConfirmation,
        CancellationToken cancellationToken)
    {
        emailConfirmation.Created = _clock.GetNow;
        await DatabaseDbContext.EmailUserActions.AddAsync(emailConfirmation, cancellationToken);
        await DatabaseDbContext.SaveChangesAsync( auditUserDetails, cancellationToken );
    }

    public async Task<EmailUserAction?> GetCurrentEmailUserAction(long userId, EmailUserActionType emailUserActionType, CancellationToken cancellationToken)
    {
        var now = _clock.GetNow;
        return await DatabaseDbContext.EmailUserActions.Where(
            x => x.UserId == userId
                 && x.EmailActionType == emailUserActionType
                 && x.Expires > now)
            .OrderByDescending( x => x.Expires)
            .FirstOrDefaultAsync(cancellationToken);
    }

    public async Task AddUser(AuditUserDetails auditUserDetails, User user, CancellationToken cancellationToken)
    {
        user.Created = _clock.GetNow;

        await DatabaseDbContext.Users.AddAsync(user, cancellationToken);
        await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken);
    }

    public async Task EditUser(AuditUserDetails auditUserDetails, User user, CancellationToken cancellationToken)
    {
        await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken);
    }

    public async Task<EmailUserAction?> GetEmailUserAction(Guid token, CancellationToken cancellationToken)
    {
        return await DatabaseDbContext.EmailUserActions
            .Where(c => c.Token.Equals(token)
                        && c.Expires > _clock.GetNow)
            .Include(c => c.User)
            .SingleOrDefaultAsync(cancellationToken);
    }

    public async Task DeleteEmailUserAction(AuditUserDetails auditUserDetails, EmailUserAction emailUserAction, CancellationToken cancellationToken)
    {
        DatabaseDbContext.EmailUserActions.Remove(emailUserAction);
        await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken);
    }

    public async Task DeleteExpiredEmailUserActions()
    {
        var expiredActions =  await DatabaseDbContext.EmailUserActions
            .Where(c => c.Expires < _clock.GetNow).ToListAsync();
        DatabaseDbContext.EmailUserActions.RemoveRange(expiredActions);
        await DatabaseDbContext.NoAuditSaveChangesAsync();
    }

    public async Task DeleteExpiredSingleUseGuids()
    {
        var expiredGuids = await DatabaseDbContext.SingleUseGuids
            .Where( x => x.Expires < _clock.GetNow).ToListAsync();
        DatabaseDbContext.SingleUseGuids.RemoveRange(expiredGuids);
        await DatabaseDbContext.NoAuditSaveChangesAsync();
    }

    public IQueryable<User> GetUsers()
    {
        return DatabaseDbContext.Users
            .Include( x => x.Domain);
    }
}