using Insight.Infrastructure.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using System.Security.Claims;

namespace Insight.Infrastructure.Services
{
    public class IdentityService
    {
        private readonly UserManager<InsightUser> _userManager;
        private readonly RoleManager<InsightRole> _roleManager;
        private readonly ILogger<IdentityService> _logger;

        public IdentityService(UserManager<InsightUser> userManager, RoleManager<InsightRole> roleManager, ILogger<IdentityService> logger)
        {
            _userManager = userManager;
            _roleManager = roleManager;
            _logger = logger;
        }

        public async Task SeedAsync()
        {
            // SEED ROLES
            if (await _roleManager.FindByNameAsync("system") is not InsightRole systemRole)
            {
                var result = await CreateRoleAsync("system");
                if (result.Succeeded is false) throw new InvalidProgramException("seeding: system role failed");

                systemRole = await _roleManager.FindByNameAsync("system") ?? throw new InvalidProgramException("seeding: system role failed");
            }

            if (await _roleManager.FindByNameAsync("administrator") is not InsightRole administratorRole)
            {
                var result = await CreateRoleAsync("administrator");
                if (result.Succeeded is false) throw new InvalidProgramException("seeding: administrator role failed");

                administratorRole = await _roleManager.FindByNameAsync("administrator") ?? throw new InvalidProgramException("seeding: administrator role failed");
            }

            if (await _roleManager.FindByNameAsync("chat") is not InsightRole chatRole)
            {
                var result = await CreateRoleAsync("chat");
                if (result.Succeeded is false) throw new InvalidProgramException("seeding: chat role failed");

                chatRole = await _roleManager.FindByNameAsync("chat") ?? throw new InvalidProgramException("seeding: chat role failed");
            }

            // SEED USERS
            if (await _userManager.FindByEmailAsync("system@insight.local") is not InsightUser systemUser)
            {
                var result = await CreateUserAsync("system@insight.local", "Replica3-Unroasted-Respect");
                if (result.Succeeded is false) throw new InvalidProgramException("seeding: system user failed");

                systemUser = await _userManager.FindByEmailAsync("system@insight.local") ?? throw new InvalidProgramException("seeding: system user failed");
            }

            if (systemUser.Roles.Any(p => p == systemRole.Id) is false)
            {
                var assign = await _userManager.AddToRoleAsync(systemUser, systemRole.Name);
                if (assign.Succeeded is false) throw new InvalidProgramException("seeding: system user roles failed");
            }
        }

        public async Task<IdentityResult> CreateUserAsync(string email, string password)
        {
            var user = new InsightUser
            {
                UserName = email,
                NormalizedUserName = email.ToUpperInvariant(),
                Email = email,
                NormalizedEmail = email.ToUpperInvariant(),
            };

            return await _userManager.CreateAsync(user, password);
        }

        public async Task<IdentityResult> CreateRoleAsync(string name)
        {
            var role = new InsightRole
            {
                Name = name,
                NormalizedName = name.ToUpperInvariant()
            };

            return await _roleManager.CreateAsync(role);
        }

        public async Task<InsightUser> LoginAsync(string email, string password, string? code = null)
        {
            if (await _userManager.FindByEmailAsync(email) is not InsightUser user) throw new InvalidDataException("Invalid Credentials");
            if (await _userManager.CheckPasswordAsync(user, password) is false) throw new InvalidDataException("Invalid Credentials");

            if (await _userManager.GetTwoFactorEnabledAsync(user))
            {
                if (string.IsNullOrWhiteSpace(code)) throw new InvalidOperationException("Requires 2FA Code");

                var authCode = code.Replace(" ", string.Empty).Replace("-", string.Empty);

                if (await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, authCode) is false)
                {
                    throw new InvalidDataException("Invalid 2FA Code");
                }
            }

            return user;
        }

        public async Task<bool> ChangePasswordAsync(InsightUser user, string current, string @new)
        {
            var result = await _userManager.ChangePasswordAsync(user, current, @new).ConfigureAwait(false);
            return result.Succeeded;
        }

        public async Task<InsightUser?> GetByEmailAsync(string key)
        {
            var result = await _userManager.FindByEmailAsync(key).ConfigureAwait(false);
            if (result is not null) return result;

            return null;
        }

        public async Task<IList<Claim>> GetClaimsAsync(InsightUser user, bool includeRoles = true)
        {
            var claims = await _userManager.GetClaimsAsync(user).ConfigureAwait(false);

            if (includeRoles)
            {
                var roles = await _userManager.GetRolesAsync(user).ConfigureAwait(false);
                foreach (var role in roles) claims.Add(new Claim(ClaimTypes.Role, role));
            }

            claims.Add(new Claim(ClaimTypes.Email, user.Email));
            claims.Add(new Claim("user", user.UserName));

            return claims;
        }
    }
}