using Domain.Identity;
using Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Models.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Services.Identity
{
    public class AuthService : BaseService<Account>, IAuthService
    {

        private readonly ITokenIssuer _tokenIssuer;

        public AuthService(IServiceProvider serviceProvider) : base(serviceProvider)
        {
            _tokenIssuer = serviceProvider.GetService<ITokenIssuer>();
        }


 

        public async Task<Account> GetByLoginToken(string username, string token)
        {
            var now = DateTime.Now;

            var loginToken = await _db.LoginTokens
                .Where(x => !x.Used && x.ExpireationDate > now)
                .Where(x => x.Token.ToLower() == token.ToLower())
                .Where(x => x.Account.Username.ToLower() == username.ToLower())
                .Include(x => x.Account).FirstOrDefaultAsync();

            if (loginToken is null) return null;

            var account = loginToken.Account;
            return account;
        }

        public async Task<(Account account, RefreshToken refreshToken)> GetByRefreshToken(string token)
        {
            var refreshToken = await _db.RefreshTokens
              .Include(x => x.LoginToken).ThenInclude(x => x.Account)
              .FirstOrDefaultAsync(x => x.Token.ToLower().Trim() == token.Trim().ToLower());

            if (refreshToken is null) return (null, null);

            return (refreshToken.LoginToken.Account, refreshToken);
        }



        public Account GetByUsername(string username)
        {
            return  GetQueryable().FirstOrDefault(x => x.Username.ToLower().Trim() == username.ToLower().Trim());
        }

        public async Task<Account> GetByUsernameAndPassword(string username, string password)
        {
            return GetQueryable().
                Where(x => x.Username.Equals(username) && x.Password.Equals(password)).Include(x => x.AccountRoles).FirstOrDefault();

        }
       

        public async Task<LoginToken> GenerateLoginToken(Account account)
        {
            var now = DateTime.Now;            
            var token = _db.LoginTokens.FirstOrDefault(x => x.AccountId == account.Id && !x.Used && x.ExpireationDate > now);

            if (token == null)
            {
                var code = RandomGenerator.GetRandomString(4);
                token = new LoginToken
                {
                    AccountId = account.Id,
                    ExpireationDate = DateTime.Now.AddMinutes(2),
                    Token = code
                };
                await _db.LoginTokens.AddAsync(token);
                SaveChanges();

            }

            return token;
        }

        public async Task<RefreshToken> GenerateRefreshToken(LoginToken loginToken)
        {
            if (loginToken.Used) return null;


            var refreshToken = loginToken.RefreshToken ?? new RefreshToken();
            loginToken.Used = true;
            refreshToken.Token = Guid.NewGuid().ToString();
            refreshToken.LoginTokenId = loginToken.Id;

            if (loginToken.RefreshToken is null)
            {
                await _db.RefreshTokens.AddAsync(refreshToken);
                SaveChanges();
            }
            else
            {
                SaveChanges();
            }

            return refreshToken;
        }

        public async Task<PlainToken> GeneratePlainToken(Guid id, string loginToken)
        {
            var account =GetById(id);
            if (account is null) return null;

            var token = await _db.LoginTokens
                .Where(x => x.AccountId == id)
                .Include(x => x.Account)
                .ThenInclude(x => x.AccountRoles)
                .ThenInclude(x => x.Role)
                .Include(x => x.RefreshToken)
                .FirstOrDefaultAsync(x => x.Token == loginToken);
            if (token is null) return null;

            var refreshToken = await GenerateRefreshToken(token);
            var plainToken = _tokenIssuer.IssuePlainToken(account);

            return plainToken;
        }

        public async Task<PlainToken> GeneratePlainToken(Account account, string loginToken)
        {
            var token = await _db.LoginTokens
                .Where(x => x.AccountId == account.Id)
                .Where(x => x.Token == loginToken)
                .Include(x => x.Account)
                .ThenInclude(x => x.AccountRoles)
                .ThenInclude(x => x.Role)
                .Include(x => x.RefreshToken)
                .FirstOrDefaultAsync();
            if (token is null) return null;

            var refreshToken = await GenerateRefreshToken(token);
            if (refreshToken is null) return null;

            var plainToken = _tokenIssuer.IssuePlainToken(account);

            return plainToken;
        }

        public async Task<PlainToken> RefreshToken(string token)
        {
            var refreshToken = await _db.RefreshTokens
                .Where(x => x.Token == token)
                .Include(x => x.LoginToken)
                .ThenInclude(x => x.Account)
                .ThenInclude(x => x.AccountRoles)
                .ThenInclude(x => x.Role)
                .FirstOrDefaultAsync();

            if (refreshToken is null || !refreshToken.LoginToken.Used) return null;
            refreshToken.Token = Guid.NewGuid().ToString();
            SaveChanges();

            var plainToken = _tokenIssuer.IssuePlainToken(refreshToken.LoginToken.Account);
            return plainToken;
        }

   

  

        public void SetDeveloperUser()
        {
            string[] developers = { "09169212241", "09161151186" };
            var users = GetQueryable().Where(x => developers.Contains(x.Username)).ToList();
            foreach (var user in users)
            {
                var role = _db.Roles.FirstOrDefault(x => x.Name == Consts.Developer);
                if (role != null)
                {
                    var accountRole = _db.AccountRoles.FirstOrDefault(x => x.RoleId == role.Id && x.AccountId == user.Id);
                    if (accountRole == null)
                        _db.AccountRoles.Add(new AccountRole
                        {
                            AccountId = user.Id,
                            RoleId = role.Id
                        });
                    SaveChanges();
                }
            }
        }
    }
    public interface IAuthService : IBaseService<Account>
    {
        Account GetByUsername(string username);
        Task<Account> GetByUsernameAndPassword(string username, string password);
        Task<Account> GetByLoginToken(string username, string token);

        Task<LoginToken> GenerateLoginToken(Account account);
        Task<RefreshToken> GenerateRefreshToken(LoginToken loginToken);
        Task<(Account account, RefreshToken refreshToken)> GetByRefreshToken(string token);

        Task<PlainToken> GeneratePlainToken(Account account, string token);

        Task<PlainToken> RefreshToken(string token);



        void SetDeveloperUser();

    }
}
