namespace Hims.Infrastructure.Services
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Dapper;
    using Domain.Entities;
    using Domain.Helpers;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;

    /// <inheritdoc />
    public class AccountCredentialServices : IAccountCredentialService
    {
        /// <summary>
        /// The SHA helper.
        /// </summary>
        private readonly IShaHelper shaHelper;

        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

        /// <summary>
        /// Initializes a new instance of the <see cref="AccountCredentialServices"/> class.
        /// </summary>
        /// <param name="unitOfWork">
        /// The unit of work.
        /// </param>
        /// <param name="shaHelper">
        /// The sha Helper.
        /// </param>
        public AccountCredentialServices(IUnitOfWork unitOfWork, IShaHelper shaHelper)
        {
            this.unitOfWork = unitOfWork;
            this.shaHelper = shaHelper;
        }

        /// <inheritdoc />
        public Task<int> CheckAsync(int accountId) => this.unitOfWork.AccountCredentials.CountAsync(m => m.AccountId == accountId && m.Active);

        /// <inheritdoc />
        public async Task<int> CreateAsync(int accountId, string password)
        {
            var account = await this.unitOfWork.Accounts.FindAsync(m => m.AccountId == accountId);
            if (account == null || account.AccountId == 0)
            {
                return 0;
            }

            var accounts = new List<Account>();
            if (!string.IsNullOrEmpty(account.Email))
            {
                var emailAccounts = await this.unitOfWork.Accounts.FindAllAsync(x => x.Email == account.Email);
                accounts.AddRange(emailAccounts);
            }

            if (!string.IsNullOrEmpty(account.Mobile))
            {
                var mobileAccounts = await this.unitOfWork.Accounts.FindAllAsync(x => x.Mobile == account.Mobile);
                foreach (var mobileAccount in mobileAccounts)
                {
                    if (accounts.All(x => x.AccountId != mobileAccount.AccountId))
                    {
                        accounts.Add(mobileAccount);
                    }
                }
            }

            var response = 0;
            foreach (var query in from subAccount in accounts
                                  let hashPassword = this.shaHelper.GenerateHash(password, subAccount.SaltKey)
                                  select $@"UPDATE ""AccountCredential"" ac SET ""Active"" = FALSE FROM ""Account"" act
                    WHERE act.""AccountId"" = ac.""AccountId"" AND act.""AccountId"" = '{subAccount.AccountId}';
                INSERT INTO ""AccountCredential""(""AccountId"", ""PasswordHash"", ""CreatedBy"", ""CreatedDate"")
                SELECT act.""AccountId"", '{hashPassword}', act.""AccountId"", NOW() AT TIME ZONE 'UTC' FROM ""Account"" act
                    WHERE act.""AccountId"" = '{subAccount.AccountId}'")
            {
                response = await this.unitOfWork.Current.ExecuteAsync(query);
            }

            return response;
        }

        /// <inheritdoc />
        public async Task<int> CreatePasswordAsync(int accountId, string password)
        {
            var account = await this.unitOfWork.Accounts.FindAsync(m => m.AccountId == accountId);
            if (account == null || account.AccountId == 0)
            {
                return 0;
            }

            var passwordHash = this.shaHelper.GenerateHash(password, account.SaltKey);
            var query = new AccountsCredential(account, passwordHash).Query;

            var inserted = await this.unitOfWork.Current.ExecuteAsync(query);

            return inserted;
        }

        public async Task<int> PatientPasswordCreateAsync(string userName, int? roleId, string password)
        {
            var account = new Account();
            if (!userName.Contains("@"))
            {
                account = await this.unitOfWork.Accounts.FindAsync(m => m.Mobile == userName && m.RoleId == roleId);
            }
            else
            {
                account = await this.unitOfWork.Accounts.FindAsync(m => m.Email == userName && m.RoleId == roleId);
            }
            if (account == null || account.AccountId == 0)
            {
                return 0;
            }

            var accounts = new List<Account>();
            if (!string.IsNullOrEmpty(account.Email))
            {
                var emailAccounts = await this.unitOfWork.Accounts.FindAllAsync(x => x.Email == account.Email && x.RoleId == account.RoleId);
                accounts.AddRange(emailAccounts);
            }

            if (!string.IsNullOrEmpty(account.Mobile))
            {
                var mobileAccounts = await this.unitOfWork.Accounts.FindAllAsync(x => x.Mobile == userName && x.RoleId == roleId);
                foreach (var mobileAccount in mobileAccounts)
                {
                    if (accounts.All(x => x.AccountId != mobileAccount.AccountId))
                    {
                        accounts.Add(mobileAccount);
                    }
                }
            }

            var response = 0;
            foreach (var query in from subAccount in accounts
                                  let hashPassword = this.shaHelper.GenerateHash(password, subAccount.SaltKey)
                                  select $@"UPDATE ""AccountCredential"" ac SET ""Active"" = FALSE FROM ""Account"" act
                    WHERE act.""AccountId"" =ac.""AccountId"" AND act.""AccountId""={subAccount.AccountId};
                INSERT INTO ""AccountCredential""(""AccountId"", ""PasswordHash"", ""CreatedBy"", ""CreatedDate"")
                SELECT act.""AccountId"", '{hashPassword}', act.""AccountId"", NOW() AT TIME ZONE 'UTC' FROM ""Account"" act
                    WHERE act.""Mobile"" = '{subAccount.Mobile}' and act.""RoleId"" = {subAccount.RoleId} AND act.""AccountId""={subAccount.AccountId}")
            {
                response = await this.unitOfWork.Current.ExecuteAsync(query);
            }

            return response;
        }
    }
}