using Microsoft.EntityFrameworkCore;
using PowderCoating.Application.Interfaces;
using PowderCoating.Core.Entities;
using PowderCoating.Infrastructure.Data;

namespace PowderCoating.Infrastructure.Services;

/// <summary>
/// Database-backed key/value store for platform-wide configuration settings managed by
/// SuperAdmins at <c>/PlatformSettings</c>. Settings are persisted in the <c>PlatformSettings</c>
/// table as free-form strings; callers are responsible for parsing typed values (booleans, ints,
/// etc.) from the returned strings. Rows are seeded at startup so that every expected key always
/// has a row — the <see cref="SetAsync"/> guard that inserts a missing row is a safety net for
/// future migrations, not the normal path.
/// </summary>
public class PlatformSettingsService : IPlatformSettingsService
{
    private readonly ApplicationDbContext _db;

    /// <summary>
    /// Constructs the service with a direct <see cref="ApplicationDbContext"/> reference.
    /// Direct context access is used (rather than a repository) because
    /// <c>PlatformSetting</c> does not inherit <c>BaseEntity</c> and therefore is not managed
    /// by the generic <c>Repository&lt;T&gt;</c> or filtered by the global query filters.
    /// </summary>
    public PlatformSettingsService(ApplicationDbContext db)
    {
        _db = db;
    }

    /// <summary>
    /// Retrieves the value for the given <paramref name="key"/>, or <c>null</c> when the key
    /// does not exist. Uses <c>AsNoTracking</c> because reads are more frequent than writes and
    /// the caller never modifies the returned value directly.
    /// </summary>
    public async Task<string?> GetAsync(string key)
    {
        var setting = await _db.PlatformSettings
            .AsNoTracking()
            .FirstOrDefaultAsync(s => s.Key == key);
        return setting?.Value;
    }

    /// <summary>
    /// Reads a platform setting as a boolean. Returns <paramref name="defaultValue"/> when
    /// the key is missing or the stored value cannot be parsed as a boolean.
    /// </summary>
    public async Task<bool> GetBoolAsync(string key, bool defaultValue = false)
    {
        var value = await GetAsync(key);
        if (value == null) return defaultValue;
        return bool.TryParse(value, out var result) ? result : defaultValue;
    }

    /// <summary>
    /// Reads a platform setting as an integer. Returns <paramref name="defaultValue"/> when
    /// the key is missing or the stored value cannot be parsed as an integer.
    /// </summary>
    public async Task<int> GetIntAsync(string key, int defaultValue)
    {
        var value = await GetAsync(key);
        if (value == null) return defaultValue;
        return int.TryParse(value, out var result) ? result : defaultValue;
    }

    /// <summary>
    /// Creates or updates the platform setting identified by <paramref name="key"/>.
    /// Records <paramref name="updatedBy"/> (typically the SuperAdmin's username) and
    /// <c>UpdatedAt</c> (UTC) for audit purposes. Inserts a new row if the key is missing —
    /// this should only happen when a new setting key is introduced without a corresponding
    /// seed migration. Calls <c>SaveChangesAsync</c> directly rather than going through
    /// <c>IUnitOfWork</c> to avoid inadvertently flushing unrelated tracked changes from the
    /// caller's scope.
    /// </summary>
    public async Task SetAsync(string key, string? value, string? updatedBy = null)
    {
        var setting = await _db.PlatformSettings.FirstOrDefaultAsync(s => s.Key == key);
        if (setting == null)
        {
            // Shouldn't happen in normal use (rows are seeded), but handle gracefully
            setting = new PlatformSetting { Key = key };
            _db.PlatformSettings.Add(setting);
        }

        setting.Value = value;
        setting.UpdatedAt = DateTime.UtcNow;
        setting.UpdatedBy = updatedBy;

        await _db.SaveChangesAsync();
    }

    /// <summary>
    /// Returns all platform settings ordered by group then key, suitable for rendering the
    /// SuperAdmin settings management UI. Results are read with <c>AsNoTracking</c> for
    /// performance; the UI renders them read-only and individual edits go through
    /// <see cref="SetAsync"/>.
    /// </summary>
    public async Task<IReadOnlyList<PlatformSetting>> GetAllAsync()
    {
        return await _db.PlatformSettings
            .AsNoTracking()
            .OrderBy(s => s.GroupName)
            .ThenBy(s => s.Key)
            .ToListAsync();
    }
}
