using System.Text.Json;
using Meezi.Core.Constants;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Platform;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace Meezi.Infrastructure.Services.Platform;

public class PlatformCatalogService : IPlatformCatalogService
{
    private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
    private const string PlansCacheKey = "platform:plans";
    private const string FeaturesCacheKey = "platform:features";
    private const string SettingsCacheKey = "platform:settings";

    private readonly AppDbContext _db;
    private readonly IMemoryCache _cache;

    public PlatformCatalogService(AppDbContext db, IMemoryCache memoryCache)
    {
        _db = db;
        _cache = memoryCache;
    }

    public void InvalidateCache()
    {
        _cache.Remove(PlansCacheKey);
        _cache.Remove(FeaturesCacheKey);
        _cache.Remove(SettingsCacheKey);
    }

    public async Task<IReadOnlyList<PlanDefinitionDto>> GetPlansAsync(CancellationToken cancellationToken = default)
    {
        var plans = await LoadPlansAsync(cancellationToken);
        return plans;
    }

    public async Task<PlanDefinitionDto?> GetPlanAsync(PlanTier tier, CancellationToken cancellationToken = default)
    {
        var plans = await LoadPlansAsync(cancellationToken);
        return plans.FirstOrDefault(p => p.Tier == tier);
    }

    public async Task<PlanLimitsData> GetLimitsAsync(PlanTier tier, CancellationToken cancellationToken = default)
    {
        var plan = await GetPlanAsync(tier, cancellationToken);
        return plan?.Limits ?? PlanLimitsData.ForTier(tier);
    }

    public async Task<decimal> GetMonthlyPriceTomanAsync(PlanTier tier, CancellationToken cancellationToken = default)
    {
        var plan = await GetPlanAsync(tier, cancellationToken);
        return plan?.MonthlyPriceToman ?? PlanPricing.MonthlyToman(tier);
    }

    public async Task<bool> IsBillableOnlineAsync(PlanTier tier, CancellationToken cancellationToken = default)
    {
        var plan = await GetPlanAsync(tier, cancellationToken);
        return plan?.IsBillableOnline ?? PlanPricing.IsBillableOnline(tier);
    }

    public async Task<IReadOnlyList<PlatformSettingDto>> GetSettingsAsync(CancellationToken cancellationToken = default)
    {
        return await _cache.GetOrCreateAsync(SettingsCacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2);
            return await _db.PlatformSettings
                .AsNoTracking()
                .OrderBy(s => s.Category)
                .ThenBy(s => s.Key)
                .Select(s => new PlatformSettingDto(s.Id, s.Key, s.Value, s.Category, s.DescriptionFa, s.UpdatedAt))
                .ToListAsync(cancellationToken);
        }) ?? [];
    }

    public async Task<string?> GetSettingAsync(string key, CancellationToken cancellationToken = default)
    {
        var settings = await GetSettingsAsync(cancellationToken);
        return settings.FirstOrDefault(s => s.Key == key)?.Value;
    }

    public async Task<IReadOnlyList<PlatformFeatureDto>> GetFeaturesAsync(CancellationToken cancellationToken = default)
    {
        return await _cache.GetOrCreateAsync(FeaturesCacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2);
            return await _db.PlatformFeatures
                .AsNoTracking()
                .OrderBy(f => f.ModuleGroup)
                .ThenBy(f => f.Key)
                .Select(f => new PlatformFeatureDto(
                    f.Id, f.Key, f.DisplayNameFa, f.DisplayNameEn, f.ModuleGroup, f.IsEnabledGlobally, f.UpdatedAt))
                .ToListAsync(cancellationToken);
        }) ?? [];
    }

    public async Task<IReadOnlyDictionary<string, bool>> GetEffectiveFeaturesForCafeAsync(
        string cafeId,
        PlanTier planTier,
        CancellationToken cancellationToken = default)
    {
        var features = await GetFeaturesAsync(cancellationToken);
        var plan = await GetPlanAsync(planTier, cancellationToken);
        var planKeys = plan?.FeatureKeys ?? [];
        var allPlan = planKeys.Contains("*");

        var overrides = await _db.CafeFeatureOverrides
            .AsNoTracking()
            .Where(o => o.CafeId == cafeId)
            .ToDictionaryAsync(o => o.FeatureKey, o => o.IsEnabled, cancellationToken);

        var result = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
        foreach (var feature in features)
        {
            if (overrides.TryGetValue(feature.Key, out var overrideEnabled))
            {
                result[feature.Key] = overrideEnabled;
                continue;
            }

            if (!feature.IsEnabledGlobally)
            {
                result[feature.Key] = false;
                continue;
            }

            result[feature.Key] = allPlan || planKeys.Contains(feature.Key, StringComparer.OrdinalIgnoreCase);
        }

        return result;
    }

    public async Task<bool> IsFeatureEnabledForCafeAsync(
        string cafeId,
        PlanTier planTier,
        string featureKey,
        CancellationToken cancellationToken = default)
    {
        var map = await GetEffectiveFeaturesForCafeAsync(cafeId, planTier, cancellationToken);
        return map.TryGetValue(featureKey, out var enabled) && enabled;
    }

    private async Task<IReadOnlyList<PlanDefinitionDto>> LoadPlansAsync(CancellationToken cancellationToken)
    {
        return await _cache.GetOrCreateAsync(PlansCacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2);
            var rows = await _db.PlatformPlanDefinitions
                .AsNoTracking()
                .Where(p => p.IsActive)
                .OrderBy(p => p.SortOrder)
                .ToListAsync(cancellationToken);

            if (rows.Count == 0)
                return DefaultPlansFromConstants();

            return rows.Select(MapPlan).ToList();
        }) ?? DefaultPlansFromConstants();
    }

    private static IReadOnlyList<PlanDefinitionDto> DefaultPlansFromConstants() =>
        Enum.GetValues<PlanTier>().Select(tier => new PlanDefinitionDto(
            tier,
            tier.ToString(),
            tier.ToString(),
            PlanPricing.MonthlyToman(tier),
            PlanPricing.IsBillableOnline(tier),
            true,
            (int)tier,
            PlanLimitsData.ForTier(tier),
            [])).ToList();

    private static PlanDefinitionDto MapPlan(PlatformPlanDefinition row)
    {
        PlanLimitsData limits;
        try
        {
            limits = JsonSerializer.Deserialize<PlanLimitsData>(row.LimitsJson, JsonOpts)
                ?? PlanLimitsData.ForTier(row.Tier);
        }
        catch
        {
            limits = PlanLimitsData.ForTier(row.Tier);
        }

        IReadOnlyList<string> featureKeys = [];
        if (!string.IsNullOrWhiteSpace(row.FeaturesJson))
        {
            try
            {
                featureKeys = JsonSerializer.Deserialize<string[]>(row.FeaturesJson, JsonOpts) ?? [];
            }
            catch
            {
                featureKeys = [];
            }
        }

        return new PlanDefinitionDto(
            row.Tier,
            row.DisplayNameFa,
            row.DisplayNameEn,
            row.MonthlyPriceToman,
            row.IsBillableOnline,
            row.IsActive,
            row.SortOrder,
            limits,
            featureKeys);
    }
}
