using BusinessLogic.Tools;
using BusinessLogic.Tools.Mail;
using BusinessLogic.Tools.Mail.MailTemplates;
using Contracts.BindingModels;
using Contracts.BusinessLogicContracts;
using Contracts.Converters;
using Contracts.Exceptions;
using Contracts.SearchModels;
using Contracts.StorageContracts;
using Contracts.ViewModels;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Mail;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace BusinessLogic.BusinessLogic
{
    public class UserLogic : IUserLogic
    {
        private readonly ILogger _logger;
        private readonly IUserStorage _userStorage;
        private readonly ITwoFactorAuthService _twoFactorAuthService;

        public UserLogic(ILogger<UserLogic> logger, IUserStorage userStorage,
            ITwoFactorAuthService twoFactorAuthService)
        {
            _logger = logger;
            _userStorage = userStorage;
            _twoFactorAuthService = twoFactorAuthService;
        }

        public string Create(UserBindingModel model)
        {
            // Проверяем модель
            _validate(model);
            var usr = _userStorage.GetElement(new() { Email = model.Email });
            if (usr is not null)
            {
                throw new AccountException("An account with that email already exists.");
            }
            // Хешируем пароль
            model.PasswordHash = PasswordHasher.Hash(model.Password!);
            model.Birthday = model.Birthday.ToUniversalTime();

            var user = _userStorage.Insert(model);
            if (user is null)
            {
                throw new Exception("Insert operation failed.");
            }

            MailSender.Send(new MailRegistration(user));

            string code = _twoFactorAuthService.GenerateCode();
            MailSender.Send(new MailTwoFactorCode(user, code));

            return JwtProvider.Generate(user);
        }

        public UserViewModel Delete(UserSearchModel model)
        {
            ArgumentNullException.ThrowIfNull(model);

            _logger.LogInformation("Delete user. Id: {0}", model.Id);
            var user = _userStorage.Delete(model);
            if (user is null)
            {
                throw new ElementNotFoundException();
            }
            MailSender.Send(new MailDeleteUser(user));

            return UserConverter.ToView(user);
        }

        public IEnumerable<UserViewModel> ReadElements(UserSearchModel? model)
        {
            _logger.LogInformation("ReadList. Id: {Id}", model?.Id);
            var list = _userStorage.GetList(model);
            if (list is null || list.Count() == 0)
            {
                _logger.LogWarning("ReadList return null list");
                return [];
            }
            _logger.LogInformation("ReadList. Count: {Count}", list.Count());

            return list.Select(UserConverter.ToView);
        }

        public UserViewModel ReadElement(UserSearchModel model)
        {
            ArgumentNullException.ThrowIfNull(model);

            _logger.LogInformation("ReadElement. Id: {0}", model.Id);
            var user = _userStorage.GetElement(model);
            if (user is null)
            {
                throw new ElementNotFoundException();
            }
            _logger.LogInformation("ReadElement find. Id: {0}", user.Id);

            return UserConverter.ToView(user);
        }

        public UserViewModel Update(UserBindingModel model)
        {
            _validate(model);

            model.PasswordHash = PasswordHasher.Hash(model.Password!);
            model.Birthday = model.Birthday.ToUniversalTime();
            var user = _userStorage.Update(model);
            if (user is null)
            {
                throw new Exception("Update operation failed.");
            }

            MailSender.Send(new MailUpdateUserData(user));

            return UserConverter.ToView(user);
        }

        public string Login(string email, string password)
        {
            _isValidEmail(email);
            var user = _userStorage.GetElement(new() { Email = email });

            if (user is null)
            {
                throw new ElementNotFoundException();
            }
            // Проверяем пароль
            _isValidPassword(password);
            if (!PasswordHasher.Verify(password, user.PasswordHash))
            {
                throw new AccountException("The passwords don't match.");
            }

            string code = _twoFactorAuthService.GenerateCode();
            MailSender.Send(new MailTwoFactorCode(user, code));

            return JwtProvider.Generate(user);
        }

        private void _validate(UserBindingModel model)
        {
            ArgumentNullException.ThrowIfNull(model);
            _isValidPassword(model.Password);
            _isValidEmail(model.Email);
        }

        private void _isValidPassword(string? password)
        {
            if (string.IsNullOrWhiteSpace(password))
            {
                throw new AccountException("The password is null.");
            }
            var hasMin8Max15Chars = new Regex(@".{8,15}");
            if (!hasMin8Max15Chars.IsMatch(password))
            {
                throw new AccountException("The password must not be less than 8 or more than 15 characters long.");
            }
        }

        private void _isValidEmail(string? email)
        {
            if (string.IsNullOrWhiteSpace(email))
            {
                throw new AccountException("The email is null.");
            }
            if (!MailAddress.TryCreate(email, out _))
            {
                throw new AccountException("The email is not valid.");
            }
        }

        public bool VerifyCode(string code)
        {
            return _twoFactorAuthService.Verify(code);
        }
    }
}