using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Infrastructure.Repositories;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;

namespace adas_core.LocalLogin;

/// <summary>
/// Repository for handling user data in the local login system. This repository provides methods for inserting, updating, and retrieving user information from a MongoDB collection. 
/// It also ensures that a system user is created during the initial load if it does not already exist.
/// </summary>
public class LocalLoginRepository : MongoRepository<User>, ILocalLoginRepository
{
    /// <summary>
    /// API settings containing configuration for the MongoDB collection name and other related settings. This is injected via the constructor and used to determine the collection name for user data.
    /// </summary>
    private readonly ApiSettings _apiSettings;
    // private readonly ILogger<LocalLoginRepository> _logger;

    /// <summary>
    /// Constructor for the LocalLoginRepository. It initializes the repository with the provided API settings and MongoDB database. 
    /// The API settings are used to determine the collection name for user data, and the database is passed to the base MongoRepository class for handling database operations.
    /// </summary>
    /// <param name="apiSettings">The API settings containing configuration for the MongoDB collection name and other related settings.</param>
    /// <param name="database">The MongoDB database instance used for database operations.</param>
    public LocalLoginRepository(
        IOptions<ApiSettings> apiSettings,
        IMongoDatabase database
    ) : base(database)
    {
        // _logger = logger;
        _apiSettings = apiSettings.Value;
    }

    /// <summary>
    /// Gets the name of the MongoDB collection for user data. This method overrides the base method from MongoRepository and returns the collection name specified in the API settings.
    /// </summary>
    /// <returns>The name of the MongoDB collection for user data.</returns>
    public override string GetCollectionName()
    {
        return _apiSettings.Users;
    }

    /// <summary>
    /// Inserts a new user into the MongoDB collection. This method takes a User object as input and attempts to insert it into the collection. If the insertion is successful, it returns true; otherwise, it logs the error and returns false.
    /// </summary>
    /// <param name="user">The user object to insert into the collection.</param>
    /// <returns>True if the insertion is successful, false otherwise.</returns>
    public async Task<bool> InsertUserOneAsync(User user)
    {
        try
        {
            await Collection.InsertOneAsync(user);
            return true;
        }
        catch (Exception e)
        {
            Log.Error("Unable to insert user. Exception: {e}", e.Message);
            return false;
        }
    }

    /// <summary>
    /// Updates an existing user in the MongoDB collection. This method takes an ObjectId representing the user's ID and a User object containing the updated information. 
    /// It constructs a filter to find the user by ID and an update definition to set the new values for the user's properties. 
    /// If the update is successful, it returns true; otherwise, it logs the error and returns false.
    /// </summary>
    /// <param name="id">The unique identifier of the user to update.</param>
    /// <param name="user">The user object containing the updated information.</param>
    /// <returns>True if the update is successful, false otherwise.</returns>
    public async Task<bool> UpdateOneUserAsync(ObjectId id, User user)
    {
        try
        {
            var filter = Builders<User>.Filter.Eq("Id", user.Id);
            var update = Builders<User>.Update
                .Set(c => c.Name, user.Name)
                .Set(c => c.Email, user.Email)
                .Set(c => c.UserName, user.UserName)
                .Set(c => c.Password, user.Password)
                .Set(c => c.LockExpirationDate, user.LockExpirationDate)
                .Set(c => c.LastLogin, user.LastLogin)
                .Set(c => c.IsEnabled, user.IsEnabled);
            await Collection.UpdateOneAsync(filter, update);

            return true;
        }
        catch (Exception e)
        {
            Log.Error("Unable to insert user. Exception: {e}", e.Message);
            return false;
        }
    }

    /// <summary>
    /// Retrieves a user from the MongoDB collection by their unique identifier (ObjectId). This method constructs a filter to find the user by their ID and returns the first matching user if found; otherwise, 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)
    {
        var filter = Builders<User>.Filter.Eq(p => p.Id, id);
        var result = await Collection.Find(filter).ToListAsync();
        return result.FirstOrDefault();
    }

    /// <summary>
    /// Retrieves a user from the MongoDB collection by their email address. This method constructs a filter to find the user by their email and returns the first matching user if found; otherwise, 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 address.</returns>
    public async Task<User?> GetByEmail(string email)
    {
        var filter = Builders<User>.Filter.Eq(p => p.Email, email);
        var result = await Collection.Find(filter).ToListAsync();
        return result.FirstOrDefault();
    }

    /// <summary>
    /// Retrieves a user from the MongoDB collection by their username. This method constructs a filter to find the user by their username and returns the first matching user if found; otherwise, it returns null.
    /// </summary>
    /// <param name="username">The username of the user to retrieve.</param>
    /// <returns>The user object if found, or null if no user is found with the given username.</returns>
    public async Task<User?> GetByUsername(string username)
    {
        var filter = Builders<User>.Filter.Eq(p => p.UserName, username);
        var users = await Collection.Find(filter).ToListAsync();
        return users.FirstOrDefault();
    }

    /// <summary>
    /// Retrieves all users from the MongoDB collection. This method executes a query to find all documents in the collection and returns a list of user objects.
    /// </summary>
    /// <returns>A list of all user objects in the collection.</returns>
    public async Task<List<User>> GetAll()
    {
        var result = await Collection.FindAsync(Builders<User>.Filter.Empty);

        return result.ToList();
    }

    /// <summary>
    /// Inserts initial load data into the MongoDB collection. This method is called during the initial setup of the repository to ensure that a system user is created if it does not already exist. 
    /// It calls the GetOrCreateSystemUser method to check for the existence of the system user and insert it if necessary.
    /// </summary>
    /// <returns></returns>
    public sealed override async Task InsertInitialLoad()
    {
        await GetOrCreateSystemUser();
    }

    /// <summary>
    /// Checks for the existence of a system user in the MongoDB collection and creates one if it does not already exist. 
    /// This method retrieves a user with the username "System" and, if no such user is found, it creates a new user with predefined credentials and inserts it into the collection.
    /// </summary>
    /// <returns>The system user object if found or created, or null if the operation fails.</returns>
    private async Task GetOrCreateSystemUser()
    {
        var user = await GetByUsername("System");
        if (user == null)
        {
            var userToInsert = new User
            {
                UserName = "System",
                Name = "System",
                Password = "$2a$12$crWa3EN1izcZBXNc81RzmOlfaYW2TPr2NdDQEWI7RzLTnA0Dd68WG"
            };
            await Collection.InsertOneAsync(userToInsert);
        }
    }
}