using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;

namespace adas_core.Infrastructure.Repositories;

/// <summary>
/// Repository implementation for managing Notice entities in MongoDB.
/// Provides CRUD operations for notices/notifications that can be displayed on screens.
/// </summary>
public class NoticeRepository : MongoRepository<Notice>, INoticeRepository
{
    private readonly ApiSettings _apiSettings;

    /// <summary>
    /// Initializes a new instance of the NoticeRepository.
    /// </summary>
    /// <param name="apiSettings">API settings containing collection names configuration.</param>
    /// <param name="database">The MongoDB database instance.</param>
    /// <exception cref="ArgumentNullException">Thrown when apiSettings is null.</exception>
    public NoticeRepository(IOptions<ApiSettings>? apiSettings, IMongoDatabase database) : base(database)
    {
        if (apiSettings != null)
            _apiSettings = apiSettings.Value;
        else
            throw new ArgumentNullException(nameof(apiSettings));
    }

    /// <summary>
    /// Gets the name of the collection for notices.
    /// </summary>
    /// <returns>The collection name from API settings.</returns>
    public override string GetCollectionName()
    {
        return _apiSettings.Notices;
    }

    /// <summary>
    /// Inserts a new notice into the database.
    /// Automatically sets the NoticeDate to UTC current date and time before inserting.
    /// </summary>
    /// <param name="notice">The Notice entity to insert.</param>
    /// <exception cref="Exception">Logs warning and silently fails on insertion error.</exception>
    public override async Task InsertOneAsync(Notice notice)
    {
        try
        {
            notice.NoticeDate = DateTime.UtcNow;
            await Collection.InsertOneAsync(notice);
        }
        catch (Exception e)
        {
            Log.Warning("Exception trying to insert  notice: {notice}. Exception {e}", notice, e);
        }
    }

    /// <summary>
    /// Deletes a notice by its ID.
    /// </summary>
    /// <param name="id">The ObjectId of the notice to delete.</param>
    /// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
    public async Task Delete(ObjectId id)
    {
        try
        {
            var filter = Builders<Notice>.Filter.Eq(x => x.Id, id);
            await Collection.DeleteOneAsync(filter, null);
        }
        catch (Exception e)
        {
            Log.Error("Exception trying to delete notice: {id}. Exception {e}", id, e);
            throw;
        }
    }

    /// <summary>
    /// Updates an existing notice with new values.
    /// </summary>
    /// <param name="notice">The Notice entity with updated values.</param>
    /// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
    public async Task Update(Notice notice)
    {
        try
        {
            await UpdateOneAsync(notice.Id, notice);
        }
        catch (Exception e)
        {
            Log.Error("Exception trying to update notice: {notice}. Exception {e}", notice, e);
            throw;
        }
    }

    /// <summary>
    /// Retrieves all notices from the database.
    /// </summary>
    /// <returns>An enumerable of all Notice entities.</returns>
    /// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
    public async Task<IEnumerable<Notice>> FindAll()
    {
        try
        {
            var result = await Collection.FindAsync(_ => true);
            return await result.ToListAsync();
        }
        catch (Exception ex)
        {
            Log.Error("Error getting all notices. Exception: {ex}", ex);
            return [];
        }
    }

    /// <summary>
    /// Retrieves a notice by its ID.
    /// </summary>
    /// <param name="id">The ObjectId of the notice to retrieve.</param>
    /// <returns>The Notice if found; otherwise, null.</returns>
    /// <exception cref="Exception">Logs errors and returns null on failure.</exception>
    public async Task<Notice?> FindById(ObjectId id)
    {
        try
        {
            var filter = Builders<Notice>.Filter.Eq(p => p.Id, id);
            var result = await Collection.FindAsync(filter);

            return await result.FirstOrDefaultAsync();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching notice by id: {id}. Exception: {ex}", id, ex);
            return null;
        }
    }

    /// <summary>
    /// Retrieves all notices for a specific date.
    /// </summary>
    /// <param name="date">The date to filter notices by.</param>
    /// <returns>An enumerable of Notice entities matching the date, or null on error.</returns>
    /// <exception cref="Exception">Logs errors and returns null on failure.</exception>
    public async Task<IEnumerable<Notice>?> FindByDate(DateTime date)
    {
        try
        {
            var filter = Builders<Notice>.Filter.Eq(p => p.NoticeDate, date);
            var result = await Collection.FindAsync(filter);

            return result.ToEnumerable();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching notice by date: {date}. Exception: {ex}", date, ex);
            return null;
        }
    }

    /// <summary>
    /// Retrieves all notices of a specific type.
    /// </summary>
    /// <param name="type">The notice type to filter by.</param>
    /// <returns>An enumerable of Notice entities matching the type, or null on error.</returns>
    /// <exception cref="Exception">Logs errors and returns null on failure.</exception>
    public async Task<IEnumerable<Notice>?> FindByType(string type)
    {
        try
        {
            var filter = Builders<Notice>.Filter.Eq(p => p.NoticeType, type);
            var result = await Collection.FindAsync(filter);

            return result.ToEnumerable();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching notice by type: {date}. Exception: {ex}", type, ex);
            return null;
        }
    }

    /// <summary>
    /// Retrieves all notices associated with a specific display.
    /// </summary>
    /// <param name="displayId">The ObjectId of the display to filter by.</param>
    /// <returns>An enumerable of Notice entities for the display, or null on error.</returns>
    /// <exception cref="Exception">Logs errors and returns null on failure.</exception>
    public async Task<IEnumerable<Notice>?> FindByDisplayId(ObjectId displayId)
    {
        try
        {
            var filter = Builders<Notice>.Filter.Eq(p => p.DisplayId, displayId);
            var result = await Collection.FindAsync(filter);

            return result.ToEnumerable();
        }
        catch (Exception ex)
        {
            Log.Error("Error searching notices by display id: {id}. Exception: {ex}", displayId, ex);
            return null;
        }
    }

    /// <summary>
    /// Creates the necessary indexes for the Notice collection.
    /// Creates compound indexes on (noticeType, noticeDate) and (noticeDate) for improved query performance.
    /// </summary>
    public override async Task CreateIndexes()
    {
        var options = new CreateIndexOptions { Background = true, Unique = false };

        var indexes = new List<CreateIndexModel<Notice>>
        {
            new("{ noticeType: 1, noticeDate: -1 }", options),
            new("{ noticeDate: -1 }", options)
        };

        await MongoUtils.EnsureIndexes(Collection, indexes);
    }
}