using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Repository for managing Device entities in MongoDB. Provides methods for finding devices by various attributes and updating device statistics.
/// </summary>
public class DeviceRepository : MongoRepository<Device>, IDeviceRepository
{
    private readonly ApiSettings _apiSettings;

    /// <summary>
    /// Initializes a new instance of the DeviceRepository class with the specified API settings and MongoDB database.
    /// </summary>
    /// <param name="apiSettings">The API settings.</param>
    /// <param name="database">The MongoDB database.</param>
    public DeviceRepository(ApiSettings apiSettings, IMongoDatabase database) : base(database)
    {
        _apiSettings = apiSettings;
    }

    /// <summary>
    /// Gets the name of the MongoDB collection for devices. The collection name is determined by the API settings, with a default value of "devices" if not specified.
    /// </summary>
    /// <returns>The name of the MongoDB collection for devices.</returns>
    public override string GetCollectionName()
    {
        return _apiSettings.Devices ?? "devices";
    }

    /// <summary>
    /// Creates indexes for the Device collection in MongoDB. 
    /// This method ensures that indexes are created for the MacAddr, SerialNumber, Uuid, and Key fields to optimize query performance. 
    /// The MacAddr index is unique, while the others are non-unique and created in the background.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    public override async Task CreateIndexes()
    {
        var options = new CreateIndexOptions { Background = true, Unique = true };
        var indexes = new List<CreateIndexModel<Device>>
        {
            new(Builders<Device>.IndexKeys.Ascending(d => d.MacAddr), options),
            new(Builders<Device>.IndexKeys.Ascending(d => d.SerialNumber), new CreateIndexOptions { Background = true }),
            new(Builders<Device>.IndexKeys.Ascending(d => d.Uuid), new CreateIndexOptions { Background = true }),
            new(Builders<Device>.IndexKeys.Ascending(d => d.Key), new CreateIndexOptions { Background = true })
        };

        await MongoUtils.EnsureIndexes(Collection, indexes);
    }

    /// <summary>
    /// Finds a device by its MAC address. This method queries the MongoDB collection for a device with the specified MAC address and returns the first matching device, or null if no match is found.
    /// </summary>
    /// <param name="deviceDtoMacAddr">The MAC address of the device to find.</param>
    /// <returns>The device with the specified MAC address, or null if not found.</returns>
    public async Task<Device?> FindByMacAddr(string deviceDtoMacAddr)
    {
        return await Collection
            .Find(d => d.MacAddr == deviceDtoMacAddr)
            .FirstOrDefaultAsync();
    }

    /// <summary>
    /// Finds a device by its serial number. This method queries the MongoDB collection for a device with the specified serial number and returns the first matching device, or null if no match is found.
    /// </summary>
    /// <param name="deviceDtoSerialNumber">The serial number of the device to find.</param>
    /// <returns>The device with the specified serial number, or null if not found.</returns>
    public async Task<Device?> FindBySerialNumber(string deviceDtoSerialNumber)
    {
        return await Collection
            .Find(d => d.SerialNumber == deviceDtoSerialNumber)
            .FirstOrDefaultAsync();
    }

    /// <summary>
    /// Finds a device by its UUID. This method queries the MongoDB collection for a device with the specified UUID and returns the first matching device, or null if no match is found.
    /// </summary>
    /// <param name="deviceDtoUuid">The UUID of the device to find.</param>
    /// <returns>The device with the specified UUID, or null if not found.</returns>
    public async Task<Device?> FindByUuid(string deviceDtoUuid)
    {
        return await Collection
            .Find(d => d.Uuid == deviceDtoUuid)
            .FirstOrDefaultAsync();
    }

    /// <summary>
    /// Finds a device by its key. This method queries the MongoDB collection for a device with the specified key and returns the first matching device, or null if no match is found.
    /// </summary>
    /// <param name="deviceDtoKey">The key of the device to find.</param>
    /// <returns>The device with the specified key, or null if not found.</returns>
    public async Task<Device?> FindByKey(string deviceDtoKey)
    {
        return await Collection
            .Find(d => d.Key == deviceDtoKey)
            .FirstOrDefaultAsync();
    }

    /// <summary>
    /// Updates the statistics of a device. This method takes the device ID and an existing DeviceDto object, and updates the corresponding fields (Battery, Connected, Ready, Name) in the MongoDB collection for the device with the specified ID. 
    /// The UpdatedAt field is also set to the current UTC time.
    /// </summary>
    /// <param name="id">The ID of the device to update.</param>
    /// <param name="deviceExist">The existing DeviceDto object containing the updated statistics.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task UpdateDeviceStats(ObjectId id, DeviceDto deviceExist)
    {
        var update = Builders<Device>.Update
            .Set(d => d.Battery, deviceExist.Battery)
            .Set(d => d.Connected, deviceExist.Connected)
            .Set(d => d.Ready, deviceExist.Ready)
            .Set(d => d.Name, deviceExist.Name)
            .Set(d => d.UpdatedAt, DateTime.UtcNow);

        await Collection.UpdateOneAsync(
            d => d.Id == id, 
            update
        );
    }
}