using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AyCode.Database.DbContexts;
using AyCode.Database.DbContexts.Users;
using AyCode.Entities.Users;
using AyCode.Interfaces.Users;
using Microsoft.EntityFrameworkCore;
using AyCode.Database.Extensions;
using AyCode.Core.Consts;
using AyCode.Core.Helpers;
using AyCode.Core.Loggers;
using AyCode.Core.Server.Loggers;
using AyCode.Database.DbSets.Users;
using AyCode.Interfaces.Addresses;
using AyCode.Interfaces.Messages;
using AyCode.Interfaces.Profiles;
using AyCode.Interfaces.ServiceProviders;
using AyCode.Interfaces.Addresses.Dtos;
using AyCode.Interfaces.Profiles.Dtos;

namespace AyCode.Database.DataLayers.Users
{
    public abstract class AcUserDalBase<TDbContext, TUser, TProfile, TUserToken, TCompany, TUserToCompany, TAddress, TEmailMessage> : AcDalBase<TDbContext>
        where TDbContext : AcDbContextBase, IAcUserDbContextBase<TUser, TProfile, TUserToken, TCompany, TUserToCompany, TAddress, TEmailMessage> 
        where TUser : class, IAcUser<TProfile, TCompany, TUserToCompany, TAddress>
        where TProfile : class, IAcProfile<TAddress>
        where TUserToken : class, IAcUserTokenBase
        where TCompany : class, IAcCompanyBase
        where TUserToCompany : class, IAcUserToCompanyBase
        where TAddress : class, IAcAddressDtoBase
        where TEmailMessage : class, IAcEmailMessageBase
    {
        public TUser? GetUserById(Guid userId, bool onlyConfirmed) => Session(x => x.GetUserById(userId, onlyConfirmed));
        public Task<TUser?> GetUserByIdAsync(Guid userId, bool onlyConfirmed) => SessionAsync(x => x.GetUserById(userId, onlyConfirmed));

        public TUser? GetUserByEmail(string? email, bool onlyConfirmed) => Session(x => x.GetUserByEmail(email, onlyConfirmed));
        public Task<TUser?> GetUserByEmailAsync(string? email, bool onlyConfirmed) => SessionAsync(x => x.GetUserByEmail(email, onlyConfirmed));

        public TUserModelDto? GetUserModelDtoById<TUserModelDto>(Guid userId, bool onlyConfirmed) where TUserModelDto : class, IAcUserModelDtoMinBase 
            => Session(ctx => ctx.GetUserModelDtoById<TUserModelDto, TUser>(userId, onlyConfirmed));
        public Task<TUserModelDto?> GetUserModelDtoByIdAsync<TUserModelDto>(Guid userId, bool onlyConfirmed) where TUserModelDto : class, IAcUserModelDtoMinBase 
            => SessionAsync(ctx => ctx.GetUserModelDtoById<TUserModelDto, TUser>(userId, onlyConfirmed));

        public TUserModelDto? GetUserModelDtoByEmail<TUserModelDto>(string email, bool onlyConfirmed) where TUserModelDto : class, IAcUserModelDtoMinBase 
            => Session(ctx => ctx.GetUserModelDtoByEmail<TUserModelDto, TUser>(email, onlyConfirmed));
        public Task<TUserModelDto?> GetUserModelDtoByEmailAsync<TUserModelDto>(string email, bool onlyConfirmed) where TUserModelDto : class, IAcUserModelDtoMinBase 
            => SessionAsync(ctx => ctx.GetUserModelDtoByEmail<TUserModelDto, TUser>(email, onlyConfirmed));

        public List<TUserModelDto> GetAllUserModelDto<TUserModelDto>() where TUserModelDto : class, IAcUserModelDtoMinBase 
            => GetAllModelDto<TUserModelDto, TUser>();
        public Task<List<TUserModelDto>> GetAllUserModelDtoAsync<TUserModelDto>() where TUserModelDto : class, IAcUserModelDtoMinBase 
            => GetAllModelDtoAsync<TUserModelDto, TUser>();

        public Task<List<TUser>> GetUsersAsync() => SessionAsync(ctx => ctx.Users.ToList());
        //public Task<List<TUserModelDto>> GetUserEmails() => SessionAsync(ctx => ctx.Users.Select(x => new UserModelDtoEmail(x)).ToList());

        
        public virtual Task<TUser?> UpdateUserAsync(TUser user) => UpdateSafeAsync(user);
        public virtual TUser? UpdateUser(TUser user) => UpdateSafe(user);
        
        public Task<bool> AddUserAsync(TUser user)
        {
            return TransactionAsync(ctx => ctx.AddUser(user));
        }

        public Task<bool> AddUserAsync(TUser user, string profileName, TAddress address, string? firstName = null, string? lastName = null)
        {
            return TransactionAsync(ctx =>
            {
                var profile = CreateProfile(profileName, address, firstName, lastName);
                user.Profile= profile;

                return ctx.AddUser(user);
            });
        }

        public bool AddUser(TUser user, string profileName, TAddress address, string? firstName = null, string? lastName = null)
        {
            return Transaction(ctx =>
            {
                var profile = CreateProfile(profileName, address, firstName, lastName);

                user.Profile = profile;

                return ctx.AddUser(user);
            });
        }

        private static TProfile CreateProfile(string profileName, TAddress address, string? firstName, string? lastName)
        {
            var profile = Activator.CreateInstance<TProfile>();
                
            profile.Id = Guid.NewGuid();
            profile.Name = profileName;
            profile.FirstName = firstName;
            profile.LastName = lastName;

            profile.Address = address;
            profile.AddressId = address.Id;

            return profile;
        }

        public Task<bool> RemoveUserAsync(TUser user) => TransactionAsync(ctx => ctx.RemoveUser(user));
        public Task<bool> RemoveUserAsync(Guid userId) => TransactionAsync(ctx => ctx.RemoveUser(userId));

        public TUser? AuthenticateUser(string? email, string? password, string refreshToken, out AcErrorCode errorCode)
        {
            TUser? user = null;
            var errorCodeInner = AcErrorCode.Unset;
            
            var result = Transaction(ctx =>
            {
                try
                {
                    //var passwordHash = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(userId));

                    user = ctx.GetUserByEmail(email, true);
                    if (user is { EmailConfirmed: true } && string.Equals(user.EmailAddress, email, StringComparison.CurrentCultureIgnoreCase)
                                                         && PasswordHasher.VerifyPassword(password, user.Password, PasswordHasher.GenerateDynamicSalt(user.Id)))
                    {
                        if (ctx.UpdateJwtRefreshToken(user, refreshToken)) return true;

                        errorCodeInner = AcErrorCode.RefreshTokenUpdateError;
                    }
                    else errorCodeInner = AcErrorCode.WrongLoginData;
                }
                catch (Exception ex)
                {
                    GlobalLogger.Error(ex.Message, ex);
                    errorCodeInner = AcErrorCode.UnknownError;
                }

                return false;
            });

            errorCode = errorCodeInner;

            return result ? user : null;
        }

        public async Task<TUser?> UpdateJwtRefreshTokenAsync(string email, string refreshToken)
        {
            TUser? user = null;

            return await TransactionAsync(ctx => (user = ctx.UpdateJwtRefreshToken(email, refreshToken)) != null) ? user : null;
        }

        public TUser? UpdateJwtRefreshToken(string email, string refreshToken)
        {
            TUser? user = null;

            return Transaction(ctx => (user = ctx.UpdateJwtRefreshToken(email, refreshToken)) != null) ? user : null;
        }

        //public UserToken CreateUserToken(Guid userId, string verificationToken)
        //{
        //    UserToken userToken = null;
        //    Transaction(ctx =>
        //        {
        //            userToken = ctx.CreateUserToken(userId, verificationToken);
        //            return userToken != null;
        //        }
        //    );

        //    return userToken;
        //}

        //public UserToken UpdateUserTokenSent(Guid userId, string verificationToken, DateTime tokenSentUtc)
        //{
        //    UserToken userToken = null;
        //    Transaction(ctx =>
        //        {
        //            userToken = ctx.UpdateUserTokenSent(userId, verificationToken, tokenSentUtc);
        //            return userToken != null;
        //        }
        //    );

        //    return userToken;
        //}

        //public UserToken GetActiveUserToken(Guid userId)
        //    => Session(ctx => ctx.GetActiveUserToken(userId));

        //public UserToken GetUserToken(Guid userId, string verificationToken)
        //    => Session(ctx => ctx.GetUserToken(userId, verificationToken));

        //public bool IsValidToken(Guid userId, string verificationToken)
        //    => Transaction(ctx => ctx.IsValidToken(userId, verificationToken));

        //public bool IsValidToken(string email, string verificationToken)
        //{
        //    return Transaction(ctx =>
        //    {
        //        var user = ctx.GetUserByEmail(email);

        //        return user != null && ctx.IsValidToken(user.Id, verificationToken);
        //    });
        //}

        //public void DeactivateTokens(Guid userId)
        //{
        //    Transaction(ctx =>
        //    {
        //        ctx.DeactivateTokens(userId);
        //        return true;
        //    });
        //}
        //public string GetValidVerificationToken(UserDal userDal, Guid userId, out UserToken userToken)
        //{
        //    string verificationToken;
        //    userToken = userDal.GetActiveUserToken(userId);

        //    if (userToken == null)
        //    {
        //        verificationToken = AntGenerator.NewToken();
        //        userToken = userDal.CreateUserToken(userId, verificationToken);
        //    }
        //    else verificationToken = userToken.Token;

        //    return verificationToken;
        //}

        public AcErrorCode GetUserForEmailValidation(string email, Func<TUser?, AcErrorCode, AcErrorCode> callback)
        {
            TUser? user = null;
            var errorCodes = AcErrorCode.Unset;

            Transaction(ctx =>
            {
                var lowerEmail= email.ToLower();

                user = ctx.Users.FirstOrDefault(x => x.EmailAddress == lowerEmail && x.EmailConfirmed);

                if (user == null)
                {
                    errorCodes = AcErrorCode.UserNameNotFound;
                    return false;
                }

                //user.Profile = ctx.Profiles.FirstOrDefault(x => x.OwnerId == user.Id);

                //if (!ctx.IsUserConsentsAccepted(user.Id)) errorCodes = AcErrorCode.ConsentNotAccepted;

                return true;
            });

            //if (user != null) 

            return callback(user, errorCodes);
        }
    }
}
