#nullable enable
using System.Globalization;
using System.Text.Json;
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Configuration;
using Baya.Application.Models.Common;
using Baya.Application.Models.Configuration;
using Baya.Domain.Entities.Audit;
using Baya.Domain.Entities.Configuration;
using Microsoft.EntityFrameworkCore;

namespace Baya.Infrastructure.Persistence.Services.Configuration;

/// <summary>
/// Cached, typed accessor over <c>platform_configs</c>. Reads go through <see cref="ICacheService"/>;
/// a write updates the row (audited by the SaveChanges interceptor in the same transaction) and evicts
/// the cache key so the next read sees the new value.
/// </summary>
internal sealed class PlatformConfigService(ApplicationDbContext db, ICacheService cache) : IPlatformConfig
{
    private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(30);

    private static string CacheKey(string key) => $"platform_config:{key}";

    public async ValueTask<T> GetConfig<T>(string key, CancellationToken cancellationToken = default)
    {
        var dto = await cache.GetOrCreateAsync(
            CacheKey(key),
            async ct => await db.Set<PlatformConfig>()
                .AsNoTracking()
                .Where(c => c.Key == key)
                .Select(c => new PlatformConfigDto(c.Key, c.Value, c.DataType, c.Description, c.ModifiedAt ?? c.CreatedAt, c.ModifiedById ?? c.CreatedById))
                .FirstOrDefaultAsync(ct),
            CacheTtl,
            cancellationToken);

        if (dto is null)
            throw new InvalidOperationException($"Platform config key '{key}' does not exist.");

        return Parse<T>(dto.Value, dto.DataType);
    }

    public async ValueTask<bool> SetConfig(string key, string value, CancellationToken cancellationToken = default)
    {
        var entity = await db.Set<PlatformConfig>().FirstOrDefaultAsync(c => c.Key == key, cancellationToken);
        if (entity is null)
            return false;

        entity.Value = value;
        await db.SaveChangesAsync(cancellationToken);

        await cache.RemoveAsync(CacheKey(key), cancellationToken);
        return true;
    }

    public async ValueTask<PagedResult<PlatformConfigDto>> ListAsync(int page, int pageSize, CancellationToken cancellationToken = default)
    {
        var query = db.Set<PlatformConfig>().AsNoTracking().OrderBy(c => c.Key);

        var total = await query.CountAsync(cancellationToken);
        var items = await query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(c => new PlatformConfigDto(c.Key, c.Value, c.DataType, c.Description, c.ModifiedAt ?? c.CreatedAt, c.ModifiedById ?? c.CreatedById))
            .ToListAsync(cancellationToken);

        return new PagedResult<PlatformConfigDto>(items, total, page, pageSize);
    }

    public async ValueTask<PagedResult<ConfigChangeDto>> GetConfigChangeHistory(string key, int page, int pageSize, CancellationToken cancellationToken = default)
    {
        var configId = await db.Set<PlatformConfig>()
            .AsNoTracking()
            .Where(c => c.Key == key)
            .Select(c => (long?)c.Id)
            .FirstOrDefaultAsync(cancellationToken);

        if (configId is null)
            return new PagedResult<ConfigChangeDto>([], 0, page, pageSize);

        var entityId = configId.Value.ToString(CultureInfo.InvariantCulture);

        var query = db.Set<AuditLog>()
            .AsNoTracking()
            .Where(a => a.EntityType == nameof(PlatformConfig) && a.EntityId == entityId)
            // Id is a monotonic identity → newest-first and deterministic (no timestamp ties).
            .OrderByDescending(a => a.Id);

        var total = await query.CountAsync(cancellationToken);
        var items = await query
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(a => new ConfigChangeDto(a.Id, a.Action, a.ChangedFieldsJson, a.ActorUserId, a.OccurredAt))
            .ToListAsync(cancellationToken);

        return new PagedResult<ConfigChangeDto>(items, total, page, pageSize);
    }

    private static T Parse<T>(string value, string dataType)
    {
        object parsed = dataType switch
        {
            ConfigDataType.Decimal => decimal.Parse(value, CultureInfo.InvariantCulture),
            ConfigDataType.Int => int.Parse(value, CultureInfo.InvariantCulture),
            ConfigDataType.Bool => bool.Parse(value),
            ConfigDataType.Json => JsonSerializer.Deserialize<T>(value)
                                   ?? throw new InvalidOperationException($"Config value for type '{typeof(T)}' deserialized to null."),
            _ => value
        };

        return (T)parsed;
    }
}
