using FoodsharingSiegen.Contracts;
using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Contracts.Helper;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Data;
using FoodsharingSiegen.Server.Service;
using FoodsharingSiegen.Shared.Helper;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace FoodsharingSiegen.Server.Auth
{
    /// <summary>
    /// The auth service class (a. beging, 04.04.2022)
    /// </summary>
    public class AuthService
    {
        #region Public Properties

        /// <summary>
        /// Gets or sets the value of the user (ab)
        /// </summary>
        public User? User => _user;

        #endregion

        #region Private Properties

        /// <summary>
        /// Gets the value of the context (ab)
        /// </summary>
        private FsContext Context { get; }

        #endregion

        #region Private Fields

        /// <summary>
        /// The authentication state provider
        /// </summary>
        private readonly AuthenticationStateProvider _authenticationStateProvider;

        /// <summary>
        /// The local storage service
        /// </summary>
        private readonly LocalStorageService _localStorageService;

        /// <summary>
        /// The user
        /// </summary>
        private User? _user;

        /// <summary>
        /// The mail service
        /// </summary>
        private readonly IMailService _mailService;

        /// <summary>
        /// The application settings
        /// </summary>
        private readonly AppSettings _appSettings;

        #endregion

        #region Setup/Teardown

        /// <summary>
        /// Initializes a new instance of the <see cref="AuthService"/> class
        /// </summary>
        /// <param name="context">The context</param>
        /// <param name="localStorageService">The local storage service</param>
        /// <param name="authenticationStateProvider">The authentication state provider</param>
        /// <param name="mailService">The mail service</param>
        public AuthService(
            FsContext context,
            LocalStorageService localStorageService,
            AuthenticationStateProvider authenticationStateProvider,
            IMailService mailService,
            IOptions<AppSettings> appSettings)
        {
            Context = context;
            _localStorageService = localStorageService;
            _authenticationStateProvider = authenticationStateProvider;
            _mailService = mailService;
            _appSettings = appSettings.Value;
        }

        #endregion

        #region Public Method Initialize

        /// <summary>
        /// Initializes this instance (a. beging, 11.04.2022)
        /// </summary>
        public async Task Initialize()
        {
            if (_user != null) return;
            
            var token = await _localStorageService.GetItem<string>(StorageKeys.TokenKey);
            if (AuthHelper.ValidateToken(token, out var user) && user != null)
                _user = user;
        }

        #endregion

        #region Public Method Login

        /// <summary>
        /// Logins the mail address (a. beging, 04.04.2022)
        /// </summary>
        /// <param name="mailAddress">The mail address</param>
        /// <param name="password">The password</param>
        /// <returns>A task containing the operation result</returns>
        public async Task<OperationResult> Login(string mailAddress, string password)
        {
            #region Ensure Admin

            var existingTroogS = await Context.Users!.AnyAsync(x => x.Mail == "fs@beging.de");
            if (!existingTroogS)
            {
                var troogs = new User
                {
                    Name = "Andre",
                    Mail = "fs@beging.de",
                    GroupsList = [UserGroup.Ambassador],
                    Type = UserType.Admin,
                    Created = DateTime.UtcNow,
                    EncryptedPassword = "qSIxTZo7J8M="
                };

                await Context.Users!.AddAsync(troogs);
                await Context.SaveChangesAsync();
            }

            #endregion Ensure Admin
            
            var encryptedPassword = Cryptor.Encrypt(password);

            _user = await Context.Users!.FirstOrDefaultAsync(x => x.Mail.ToLower() == mailAddress.ToLower() && x.EncryptedPassword == encryptedPassword);
            
            if (_user != null)
            {
                if (_user.Type == UserType.Unverified)
                {
                    _user = null;
                    return new OperationResult(new Exception("Anmeldung nicht möglich."));
                }

                var serializedToken = AuthHelper.CreateToken(_user);
                await _localStorageService.SetItem(StorageKeys.TokenKey, serializedToken);
                
                if (_user.ForceLogout)
                {
                    _user.ForceLogout = false;
                    await Context.SaveChangesAsync();
                }
                
                Context.Entry(_user).State = EntityState.Detached;
                
                return new OperationResult();
            }

            return new OperationResult(new Exception("E-Mail-Adresse oder Passwort ist ungültig."));
        }

        #endregion

        #region Public Method Logout

        /// <summary>
        /// Logouts this instance (a. beging, 04.04.2022)
        /// </summary>
        /// <returns>A task containing the operation result</returns>
        public async Task<OperationResult> Logout()
        {
            try
            {
                await _localStorageService.RemoveItem(StorageKeys.TokenKey);
                _user = null;
                ((TokenAuthStateProvider) _authenticationStateProvider).MarkUserAsLoggedOut();
                return new OperationResult();
            }
            catch (Exception e)
            {
                return new OperationResult(e);
            }
        }

        #endregion

        #region Public Method RefreshState

        /// <summary>
        /// Refreshes the state (a. beging, 21.05.2022)
        /// </summary>
        public async Task RefreshState()
        {
            if (_user == null) return;

            _user = await Context.Users?.FirstOrDefaultAsync(x => x.Id == _user.Id)!;

            if (_user != null)
            {
                var serializedToken = AuthHelper.CreateToken(_user);
                await _localStorageService.SetItem(StorageKeys.TokenKey, serializedToken);
            }
        }

        #endregion

        #region Password Recovery

        public async Task InitiateInitialPasswordSetup(string email, string baseUri)
        {
            if (string.IsNullOrWhiteSpace(email)) return;

            var user = await Context.Users!.FirstOrDefaultAsync(x => x.Mail.ToLower() == email.ToLower());
            if (user == null) return; // Do not leak existence

            var resetToken = Guid.NewGuid().ToString("N");
            user.ResetToken = resetToken;
            user.ResetTokenExpiry = DateTime.UtcNow.AddDays(7);

            await Context.SaveChangesAsync();

            var resetLink = $"{baseUri.TrimEnd('/')}/reset-password/{resetToken}";
            var mailBody = $"""
                Hallo {user.Name},<br>
                <br>
                für dich wurde ein neues Konto bei {_appSettings.Terms.Title} erstellt. <br>
                <br>
                Um dein Passwort festzulegen, klicke bitte auf den folgenden Link (dieser ist 7 Tage gültig):<br>
                <a href='{resetLink}'>{resetLink}</a><br>
                <br>
                Viele Grüße<br>Dein Team {_appSettings.Terms.Title}
                """;

            await _mailService.SendEmailAsync(user.Mail, "Passwort festlegen", mailBody);
        }

        public async Task InitiatePasswordReset(string email, string baseUri)
        {
            if (string.IsNullOrWhiteSpace(email)) return;

            var user = await Context.Users!.FirstOrDefaultAsync(x => x.Mail.ToLower() == email.ToLower());
            if (user == null) return; // Do not leak existence

            var resetToken = Guid.NewGuid().ToString("N");
            user.ResetToken = resetToken;
            user.ResetTokenExpiry = DateTime.UtcNow.AddMinutes(30);

            Context.Audits?.Add(new Audit
            {
                Created = DateTime.Now,
                Type = AuditType.RequestPasswordReset,
                UserID = user.Id,
                Data1 = user.Mail
            });

            await Context.SaveChangesAsync();

            var resetLink = $"{baseUri.TrimEnd('/')}/reset-password/{resetToken}";
            var mailBody = $"""
                Hallo {user.Name},<br>
                <br>
                für dein Konto wurde eine Anfrage zum Zurücksetzen des Passworts gestellt. <br>
                Wenn du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren und dein Passwort bleibt unverändert.<br>
                <br>
                Um dein Passwort zurückzusetzen, klicke bitte auf den folgenden Link (dieser ist 30 Minuten gültig):<br>
                <a href='{resetLink}'>{resetLink}</a><br>
                <br>
                Viele Grüße<br>Dein Team {_appSettings.Terms.Title}
                """;

            await _mailService.SendEmailAsync(user.Mail, "Passwort zurücksetzen", mailBody);
        }

        public async Task<OperationResult> ResetPassword(string token, string newPassword)
        {
            if (string.IsNullOrWhiteSpace(token)) return new OperationResult(new Exception("Ungültiges Token."));
            if (string.IsNullOrWhiteSpace(newPassword)) return new OperationResult(new Exception("Passwort darf nicht leer sein."));

            var user = await Context.Users!.FirstOrDefaultAsync(x => x.ResetToken == token && x.ResetTokenExpiry > DateTime.UtcNow);
            if (user == null) return new OperationResult(new Exception("Token ist ungültig oder abgelaufen."));

            user.Password = newPassword;
            user.ResetToken = null;
            user.ResetTokenExpiry = null;

            await Context.SaveChangesAsync();

            return new OperationResult();
        }

        public async Task<bool> IsResetTokenValid(string token)
        {
            if (string.IsNullOrWhiteSpace(token)) return false;
            var user = await Context.Users!.FirstOrDefaultAsync(x => x.ResetToken == token && x.ResetTokenExpiry > DateTime.UtcNow);
            return user != null;
        }

        #endregion
    }
}