using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace AsbCloudInfrastructure.Services
{
    public class AuthService : IAuthService
    {
        private readonly IAsbCloudDbContext db;

        public const string issuer = "a";
        public const string audience = "a";
        public static readonly SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("супер секретный ключ для шифрования"));
        public const string algorithms = SecurityAlgorithms.HmacSha256;

        private static readonly TimeSpan expiresTimespan = TimeSpan.FromDays(365.25);
        private static readonly Encoding encoding = Encoding.UTF8;
        private const int PasswordSaltLength = 5;
        private const string claimNameidCompany = "idCompany";
        private readonly HashAlgorithm hashAlgoritm;
        private readonly Random rnd;

        public AuthService(IAsbCloudDbContext db)
        {
            this.db = db;
            hashAlgoritm = SHA384.Create();
            rnd = new Random((int)(DateTime.Now.Ticks % 2147480161));
        }

        public UserTokenDto Login(string login, string password)
        {
            var identity = GetClaimsUser(login, password);
            if (identity == default)
                return null;

            return new UserTokenDto
            {
                Id = identity.User.Id,
                Name = identity.User.Name,
                CompanyName = identity.User.Company.Caption,
                Level = identity.User.Level,
                Login = identity.User.Login,
                Patronymic = identity.User.Patronymic,
                RoleName = identity.User.Role.Caption,
                Surname = identity.User.Surname,
                Token = MakeToken(identity.Identity.Claims),
            };
        }

        public string Refresh(ClaimsPrincipal user)
        {
            return MakeToken(user.Claims);
        }

        public int Register(UserDto userDto)
        {
            if (userDto.Login.Length < 3)
                return -1;

            if (userDto.Password.Length < 3)
                return -2;

            var salt = GenerateSalt();

            var user = new User
            {
                IdCompany = userDto.IdCompany,
                IdRole = userDto.IdRole,
                Name = userDto.Name,
                Surname = userDto.Surname,
                Patronymic = userDto.Patronymic,
                Level = userDto.Level,
                Login = userDto.Login,
                PasswordHash = salt + ComputeHash(salt, userDto.Password)
            };

            db.Users.Add(user);
            try
            {
                db.SaveChanges();
            }
            catch //(Exception ex)
            {
                return -6;
            }

            return 0;
        }

        public int ChangePassword(string userLogin, string newPassword)
        {
            var user = db.Users.FirstOrDefault(u => u.Login == userLogin);
            if (user == null)
                return -1;

            var salt = GenerateSalt();
            user.PasswordHash = salt + ComputeHash(salt, newPassword);
            db.SaveChanges();
            return 0;
        }

        public int ChangePassword(int idUser, string newPassword)
        {
            var user = db.Users.FirstOrDefault(u => u.Id == idUser);
            if (user == null)
                return -1;

            var salt = GenerateSalt();
            user.PasswordHash = salt + ComputeHash(salt, newPassword);
            db.SaveChanges();
            return 0;
        }

        private static string MakeToken(IEnumerable<Claim> claims)
        {
            var now = DateTime.Now;

            var jwt = new JwtSecurityToken(
                    issuer: issuer,
                    audience: audience,
                    notBefore: now,
                    claims: claims,
                    expires: now.Add(expiresTimespan),
                    signingCredentials: new SigningCredentials(securityKey, algorithms));

            return new JwtSecurityTokenHandler().WriteToken(jwt);
        }

        private (ClaimsIdentity Identity, User User) GetClaimsUser(string login, string password)
        {
            var user = db
                .GetUsersByLogin(login)
                .FirstOrDefault();

            if (user is null)
                return default;

            if (!CheckPassword(user.PasswordHash, password))
                return default;

            var claims = new List<Claim>
                {
                    new Claim(ClaimsIdentity.DefaultNameClaimType, user.Login),
                    new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role?.Caption??"GUEST"),
                    new Claim(claimNameidCompany, user.IdCompany.ToString()),
                };
            var claimsIdentity = new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
            return (claimsIdentity, user);
        }

        private bool CheckPassword(string passwordHash, string password)
        {
            if (passwordHash.Length == 0 && password.Length == 0)
                return true;

            if (passwordHash.Length < PasswordSaltLength)
                return false;

            var salt = passwordHash[0..PasswordSaltLength];
            var hashDb = passwordHash[PasswordSaltLength..];

            return hashDb == ComputeHash(salt, password);
        }

        private string ComputeHash(string salt, string password)
        {
            var hashBytes = hashAlgoritm.ComputeHash(encoding.GetBytes(salt + password));
            var hashString = BitConverter.ToString(hashBytes)
                    .Replace("-", "")
                    .ToLower();
            return hashString;
        }

        public string GenerateSalt()
        {
            const string saltChars = "sHwiaX7kZT1QRp0cPILGUuK2Sz=9q8lmejDNfoYCE3B_WtgyVv6M5OxAJ4Frbhnd";
            string salt = "";
            for (int i = 0; i < PasswordSaltLength - 1; i++)
                salt += saltChars[rnd.Next(0, saltChars.Length)];
            salt += "|";
            return salt;
        }
    }
}