using System.IdentityModel.Tokens.Jwt;
using AyCode.Core.Helpers;
using AyCode.Interfaces.Addresses;
using AyCode.Interfaces.Logins;
using AyCode.Interfaces.Profiles;
using AyCode.Interfaces.Server.Logins;
using AyCode.Interfaces.ServiceProviders;
using AyCode.Interfaces.Users;
using AyCode.Services.Logins;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using AyCode.Database.DataLayers;
using Microsoft.EntityFrameworkCore;
using AyCode.Database.DbContexts;
using AyCode.Core.Consts;
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Core.Server.Loggers;
using AyCode.Database.DataLayers.Users;
using AyCode.Database.DbContexts.Users;
using AyCode.Interfaces.Messages;
using AyCode.Models.Server.Logins;
using AyCode.Utils.Extensions;
using Microsoft.Extensions.Configuration;

namespace AyCode.Services.Server.Logins;

public class AcLoginServiceServer<TResultLoggedInModel, TDal, TDbContext, TUser, TUserToken, TProfile, TCompany, TUserToCompany, TProfileAddress, TEmailMessage>(TDal userDal, IConfiguration configuration)
    : AcLoginServiceBase<TUser, TProfile, TCompany, TUserToCompany, TProfileAddress>, IAcLoginServiceServer<TResultLoggedInModel, TUser, TUserToken, TProfile, TCompany, TUserToCompany, TProfileAddress>

    where TResultLoggedInModel : class, IAcLoggedInModelBase<TUser, TUserToken, TProfile, TCompany, TUserToCompany, TProfileAddress>
    where TDal : AcUserDalBase<TDbContext, TUser, TProfile, TUserToken, TCompany, TUserToCompany, TProfileAddress, TEmailMessage>
    where TDbContext : AcDbContextBase, IAcUserDbContextBase<TUser, TProfile, TUserToken, TCompany, TUserToCompany, TProfileAddress, TEmailMessage>
    where TUser : class, IAcUser<TProfile, TCompany, TUserToCompany, TProfileAddress>
    where TUserToken : class, IAcUserTokenBase
    where TProfile : class, IAcProfile<TProfileAddress>
    where TCompany : class, IAcCompanyBase
    where TUserToCompany : class, IAcUserToCompanyBase
    where TProfileAddress : class, IAcAddress
    where TEmailMessage : class, IAcEmailMessageBase
{
    public TResultLoggedInModel? LoggedInModel { get; private set; }
    public override TUser? LoggedInUser => LoggedInModel?.LoggedInUser;
    public override bool IsLoggedIn => LoggedInModel?.IsLoggedIn ?? false;

    public virtual TResultLoggedInModel Login(string? email, string? password)
    {
        if (LoggedInModel != null) Logout();

        LoggedInModel = Activator.CreateInstance<TResultLoggedInModel>();

        if (AcValidate.IsValidEmailAndPasswordFormat(email, password, out var errorCode))
        {
            var user = userDal.AuthenticateUser(email, password, GenerateRefreshToken(), out errorCode);

            if (user != null) LoggedInModel.AddLoggedInUser(user, GenerateAccessToken(user));
        }

        LoggedInModel.LoginErrorCode = errorCode;

        return LoggedInModel;
    }

    public virtual Task<TResultLoggedInModel> LoginAsync(string? email, string? password)
    {
        return TaskHelper.ToThreadPoolTask(() => Login(email, password));
    }

    public virtual bool Logout()
    {
        LoggedInModel?.Logout();
        LoggedInModel = null;

        return true;
    }

    public virtual Task<bool> LogoutAsync()
    {
        return TaskHelper.ToThreadPoolTask(Logout);
    }

    public virtual AcErrorCode Registration(string email, string password, string? phoneNumber = null, Guid? referralId = null)
        => Registration(Guid.NewGuid(), email, password, phoneNumber, referralId);

    public virtual AcErrorCode Registration(Guid userId, string email, string password, string? phoneNumber = null, Guid? referralId = null)
    {
        email = email.ToLower();

        if ((phoneNumber != null && !AcValidate.IsValidPhoneNumberFormat(phoneNumber, out var errorCode)) ||
            !AcValidate.IsValidEmailAndPasswordFormat(email, password, out errorCode)) return errorCode;

        var user = Activator.CreateInstance<TUser>();
        user.Id = userId;
        user.EmailAddress = email;
        user.EmailConfirmed = true;
        user.Password = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(userId));
        user.RefferalId = referralId;

        var address = Activator.CreateInstance<TProfileAddress>();
        address.Id = Guid.NewGuid();

        var userName = email.Split('@')[0]; //TODO: generálni egy nevet... - J.

        if (!userDal.AddUser(user, userName, address)) errorCode = AcErrorCode.DatabaseError;

        return errorCode;
    }


    public virtual Task<AcErrorCode> RegistrationAsync(string email, string password, string? phoneNumber = null, Guid? referralId = null)
        => RegistrationAsync(Guid.NewGuid(), email, password, phoneNumber, referralId);

    public virtual Task<AcErrorCode> RegistrationAsync(Guid userId, string email, string password, string? phoneNumber = null, Guid? referralId = null)
        => TaskHelper.ToThreadPoolTask(() => Registration(userId, email, password, phoneNumber, referralId));


    public virtual Task<AcErrorCode> ChangePasswordAsync(Guid userId, string oldPassword, string newPassword)
        => TaskHelper.ToThreadPoolTask(() => ChangePassword(userId, oldPassword, newPassword));

    public virtual AcErrorCode ChangePassword(Guid userId, string oldPassword, string newPassword)
    {
        try
        {
            if (userId.IsNullOrEmpty()) return AcErrorCode.IdIsNullOrEmpty;
            if (!AcValidate.IsValidPasswordFormat(newPassword, out var errorCode)) return errorCode;

            var user = userDal.GetUserById(userId, false); //TODO: csak az EmailConfirmed user password-öket lehessen change-elni! - J.
            if (user == null) return AcErrorCode.EntityIsNull;

            return PasswordHasher.VerifyPassword(oldPassword, user.Password, PasswordHasher.GenerateDynamicSalt(user.Id)) ? UpdatePassword(user, newPassword) : AcErrorCode.WrongLoginData;
        }
        catch (Exception)
        {
            // ignored
        }

        return AcErrorCode.UnknownError;
    }


    public Task<AcErrorCode> ForgotPasswordAsync(string email, string newPassword)
        => TaskHelper.ToThreadPoolTask(() => ForgotPassword(email, newPassword));

    public AcErrorCode ForgotPassword(string email, string newPassword)
    {
        try
        {
            if (email.IsNullOrEmpty()) return AcErrorCode.EmailIsNullOrEmpty;
            if (!AcValidate.IsValidPasswordFormat(newPassword, out var errorCode)) return errorCode;

            var user = userDal.GetUserByEmail(email, false);
            return user == null ? AcErrorCode.EntityIsNull : UpdatePassword(user, newPassword);
        }
        catch (Exception)
        {
            // ignored
        }

        return AcErrorCode.UnknownError;
    }


    public AcErrorCode UpdatePassword(TUser user, string password)
    {
        user.Password = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(user.Id));

        return userDal.UpdateUser(user) == null ? AcErrorCode.DatabaseError : AcErrorCode.Unset;
    }

    public virtual bool SendConfirmationToken(string? email, string confirmationToken)
    {
        //var sendGrid = SendGrid.SendGridClient();
        return true;
    }

    public virtual Task<bool> SendConfirmationTokenAsync(string email, string confirmationToken)
        => TaskHelper.ToThreadPoolTask(() => SendConfirmationToken(email, confirmationToken));

    public string GenerateAccessToken(TUser user)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        //GlobalLogger.Detail("----------------------------------------------------------");

        if (configuration["JWT:Key"] == null)
            throw new SecurityTokenException("Token is null");

        var keyDetail = Encoding.UTF8.GetBytes(configuration["JWT:Key"] ?? string.Empty);
        // SECURITY: A következő DEBUG-only sor a JWT aláíró kulcsot logolja.
        // Ha bármely log writer (fájl, HTTP, központi log-aggregator, bug-report
        // dump) perzisztálja vagy szállítja, a kulcs kiszivároghat → bárki valid
        // tokent gyárthat tetszőleges felhasználó nevében. Production build-ből
        // kötelezően kimarad (#if DEBUG). NEM törölve, mert helyi auth-debug
        // során hasznos lehet a kulcs-csere ellenőrzéséhez.
#if DEBUG
        GlobalLogger.Detail($"Key: {configuration["JWT:Key"]}");
#endif

        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new(ClaimTypes.Email, user.EmailAddress)
        };

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Audience = configuration["JWT:Audience"],
            Issuer = configuration["JWT:Issuer"],
            Expires = DateTime.UtcNow.AddHours(6),
            //Expires = DateTime.UtcNow.AddSeconds(30),
            Subject = new ClaimsIdentity(claims),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyDetail), SecurityAlgorithms.HmacSha256Signature)
        };

        var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;
        var writtenToken = tokenHandler.WriteToken(token);
        // SECURITY: A következő DEBUG-only sor a kibocsátott access token-t
        // logolja. Ha a log perzisztálódik vagy elérhető (bug-report dump,
        // log-aggregator, shared dev environment), a token lejáratáig bárki
        // a felhasználó nevében authentikálhat — JWT esetén a token önmagában
        // bearer credential. Production build-ből kötelezően kimarad
        // (#if DEBUG). NEM törölve, mert helyi auth-debug során hasznos.
#if DEBUG
        GlobalLogger.Detail($"AccessToken: {writtenToken}");
#endif

        return writtenToken;
    }

    public ClaimsPrincipal? GetPrincipalFromExpiredToken(string? token)
    {
        if (token.IsNullOrWhiteSpace()) return null;

        var tokenHandler = new JwtSecurityTokenHandler();

        if (configuration["JWT:Key"] == null)
            throw new SecurityTokenException("Token is null");

        var keyDetail = Encoding.UTF8.GetBytes(configuration["JWT:Key"] ?? string.Empty);

        var tokenValidationParameter = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = false,
            ValidateIssuerSigningKey = true,
            ValidIssuer = configuration["JWT:Issuer"],
            ValidAudience = configuration["JWT:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(keyDetail),
        };

        var principal = tokenHandler.ValidateToken(token, tokenValidationParameter, out var securityToken);

        if (securityToken is not JwtSecurityToken jwtSecurityToken || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
            throw new SecurityTokenException("Invalid token");

        return principal;
    }

    public string GenerateRefreshToken()
    {
        var randomNumber = new byte[32];

        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(randomNumber);

        return Convert.ToBase64String(randomNumber);
    }
}