using adas_core.Application.Exceptions;
using adas_core.Authentication.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Exceptions;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using Serilog;

namespace adas_core.LocalLogin;

/// <summary>
/// Service implementation for local login functionality. This service handles user authentication using a local repository and authority service to manage user credentials and permissions.
/// </summary>
/// <param name="localLoginRepository">The repository used to access local user data.</param>
/// <param name="authorityService">The service used to manage user authorities and permissions.</param>
public class LocalLoginServiceImpl(
    ILocalLoginRepository localLoginRepository,
    IAuthorityService authorityService)
    : ILoginService
{
    /// <summary>
    /// Indicates whether password-based login is allowed for this service. For local login, this is always true.
    /// </summary>
    public bool AllowPassword => true;

    /// <summary>
    /// Indicates the login method used by this service, which is local login in this case.
    /// </summary>
    public UserEnum.LoginMethod Method => UserEnum.LoginMethod.Local;

    /// <summary>
    /// Authenticates a user based on the provided username and password. It retrieves the user from the local repository, verifies the password, and if successful, retrieves the user's authorities before returning the user object. 
    /// If authentication fails, it throws an UnauthorizedException with an appropriate error message.
    /// </summary>
    /// <param name="username">The username of the user to authenticate.</param>
    /// <param name="password">The password of the user to authenticate.</param>
    /// <returns>The authenticated user object.</returns>
    /// <exception cref="UnauthorizedException">Thrown when the username or password is invalid.</exception>
    public async Task<User> Login(string username, string password)
    {
        var userFound = await localLoginRepository.GetByUsername(username) ??
                        throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedInvalidCredentials);

        if (!CryptoAdas.VerifyPassword(password, userFound.Password))
            throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedInvalidCredentials);

        userFound.Authorization = await authorityService.GetUserAuthorities(userFound.Id);
        return userFound;
    }

    /// <summary>
    /// Authenticates a user based on the provided HTTP context. This method is not implemented for local login and will throw a LoginServicesException if called.
    /// </summary>
    /// <param name="context">The HTTP context used for authentication.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    /// <exception cref="LoginServicesException">Thrown when this method is called, as it is not implemented for local login.</exception>
    public Task<User> Login(HttpContext context)
    {
        throw new LoginServicesException("Not implemented");
    }

    /// <summary>
    /// Authenticates a user based on the provided username and password. This method is similar to the Login method but does not throw exceptions for invalid credentials. 
    /// Instead, it returns an empty User object if authentication fails. This can be useful in scenarios where you want to check credentials without throwing exceptions.
    /// </summary>
    /// <param name="username">The username of the user to authenticate.</param>
    /// <param name="password">The password of the user to authenticate.</param>
    /// <returns>The authenticated user object, or an empty User object if authentication fails.</returns>
    public async Task<User> Authenticate(string username, string password)
    {
        try
        {
            var userFound = await localLoginRepository.GetByUsername(username);

            if (userFound != null && CryptoAdas.VerifyPassword(password, userFound.Password)) return userFound;

            return new User();
        }
        catch (Exception e)
        {
            Log.Error("Error Authenticate with username-password: {username}: Exception: {exception}", username, e.Message);
            throw;
        }
    }

    /// <summary>
    /// Retrieves a user by their unique identifier (ObjectId). This method queries the local repository for a user with the specified ID and returns the user object if found. If no user is found with the given ID, it returns null.
    /// </summary>
    /// <param name="id">The unique identifier of the user to retrieve.</param>
    /// <returns>The user object if found, or null if no user is found with the given ID.</returns>
    public async Task<User?> GetById(ObjectId id)
    {
        return await localLoginRepository.GetById(id);
    }

    /// <summary>
    /// Retrieves a user by their email address. This method queries the local repository for a user with the specified email and returns the user object if found. If no user is found with the given email, it returns null.
    /// </summary>
    /// <param name="email">The email address of the user to retrieve.</param>
    /// <returns>The user object if found, or null if no user is found with the given email.</returns>
    public async Task<User?> GetByEmail(string email)
    {
        return await localLoginRepository.GetByEmail(email);
    }

    /// <summary>
    /// Retrieves a user by their username. This method queries the local repository for a user with the specified username and returns the user object if found. If no user is found with the given username, it returns null.
    /// </summary>
    /// <param name="username"></param>
    /// <returns></returns>
    public async Task<User?> GetByUsername(string username)
    {
        return await localLoginRepository.GetByUsername(username);
    }

    /// <summary>
    /// Retrieves a list of all users from the local repository. This method queries the repository for all user records and returns them as a list. If there are no users in the repository, it returns an empty list.
    /// </summary>
    /// <returns>A list of all user objects, or an empty list if no users are found.</returns>
    public async Task<List<User>> GetAllUsers()
    {
        return await localLoginRepository.GetAll();
    }
}