using System.Text.Json;
using Meezi.Admin.API.Models;
using Meezi.Infrastructure.Services.Platform;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Core.Platform;
using Meezi.Core.Discover;
using Meezi.Infrastructure.Data;
using Meezi.Infrastructure.Discover;
using Microsoft.EntityFrameworkCore;

namespace Meezi.Admin.API.Services;

public interface IAdminPlatformService
{
    Task<AdminDashboardStatsDto> GetDashboardStatsAsync(CancellationToken cancellationToken = default);
    Task<IReadOnlyList<PlanDefinitionDto>> GetPlansAsync(CancellationToken cancellationToken = default);
    Task<bool> UpdatePlanAsync(PlanTier tier, UpdatePlanRequest request, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<PlatformSettingDto>> GetSettingsAsync(CancellationToken cancellationToken = default);
    Task<bool> UpdateSettingAsync(string key, UpdateSettingRequest request, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<PlatformFeatureDto>> GetFeaturesAsync(CancellationToken cancellationToken = default);
    Task<bool> UpdateFeatureAsync(string featureKey, UpdateFeatureRequest request, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<AdminCafeListItemDto>> ListCafesAsync(CancellationToken cancellationToken = default);
    Task<bool> PatchCafeAsync(string cafeId, AdminCafePatchRequest request, CancellationToken cancellationToken = default);
    Task<bool> SetCafeFeatureOverrideAsync(string cafeId, CafeFeatureOverrideRequest request, CancellationToken cancellationToken = default);
    Task<AdminCafeDiscoverProfileDto?> GetCafeDiscoverProfileAsync(string cafeId, CancellationToken cancellationToken = default);
    Task<AdminCafeDiscoverProfileDto?> UpsertCafeDiscoverProfileAsync(
        string cafeId,
        AdminUpsertCafeDiscoverProfileRequest request,
        CancellationToken cancellationToken = default);
}

public class AdminPlatformService : IAdminPlatformService
{
    private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };

    private readonly AppDbContext _db;
    private readonly IPlatformCatalogService _catalog;
    private readonly IPlatformRuntimeConfig _runtime;

    public AdminPlatformService(
        AppDbContext db,
        IPlatformCatalogService catalog,
        IPlatformRuntimeConfig runtime)
    {
        _db = db;
        _catalog = catalog;
        _runtime = runtime;
    }

    public async Task<AdminDashboardStatsDto> GetDashboardStatsAsync(CancellationToken cancellationToken = default)
    {
        var total = await _db.Cafes.CountAsync(cancellationToken);
        var suspended = await _db.Cafes.CountAsync(c => c.IsSuspended, cancellationToken);
        var openTickets = await _db.SupportTickets.CountAsync(
            t => t.Status != SupportTicketStatus.Closed && t.Status != SupportTicketStatus.Resolved,
            cancellationToken);
        var plans = await _db.PlatformPlanDefinitions.CountAsync(cancellationToken);

        return new AdminDashboardStatsDto(
            total,
            total - suspended,
            suspended,
            openTickets,
            plans);
    }

    public Task<IReadOnlyList<PlanDefinitionDto>> GetPlansAsync(CancellationToken cancellationToken = default) =>
        _catalog.GetPlansAsync(cancellationToken);

    public async Task<bool> UpdatePlanAsync(PlanTier tier, UpdatePlanRequest request, CancellationToken cancellationToken = default)
    {
        var plan = await _db.PlatformPlanDefinitions.FirstOrDefaultAsync(p => p.Tier == tier, cancellationToken);
        if (plan is null)
        {
            plan = new PlatformPlanDefinition { Tier = tier };
            _db.PlatformPlanDefinitions.Add(plan);
        }

        plan.DisplayNameFa = request.DisplayNameFa.Trim();
        plan.DisplayNameEn = request.DisplayNameEn?.Trim();
        plan.MonthlyPriceToman = request.MonthlyPriceToman;
        plan.IsBillableOnline = request.IsBillableOnline;
        plan.IsActive = request.IsActive;
        plan.SortOrder = request.SortOrder;
        plan.LimitsJson = JsonSerializer.Serialize(request.Limits, JsonOpts);
        plan.FeaturesJson = request.FeatureKeys is null
            ? null
            : JsonSerializer.Serialize(request.FeatureKeys, JsonOpts);

        await _db.SaveChangesAsync(cancellationToken);
        _catalog.InvalidateCache();
        return true;
    }

    public Task<IReadOnlyList<PlatformSettingDto>> GetSettingsAsync(CancellationToken cancellationToken = default) =>
        _catalog.GetSettingsAsync(cancellationToken);

    public async Task<bool> UpdateSettingAsync(string key, UpdateSettingRequest request, CancellationToken cancellationToken = default)
    {
        var setting = await _db.PlatformSettings.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
        if (setting is null) return false;

        setting.Value = request.Value;
        if (request.DescriptionFa is not null)
            setting.DescriptionFa = request.DescriptionFa;
        setting.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(cancellationToken);
        _catalog.InvalidateCache();
        _runtime.InvalidateCache();
        return true;
    }

    public Task<IReadOnlyList<PlatformFeatureDto>> GetFeaturesAsync(CancellationToken cancellationToken = default) =>
        _catalog.GetFeaturesAsync(cancellationToken);

    public async Task<bool> UpdateFeatureAsync(string featureKey, UpdateFeatureRequest request, CancellationToken cancellationToken = default)
    {
        var feature = await _db.PlatformFeatures.FirstOrDefaultAsync(f => f.Key == featureKey, cancellationToken);
        if (feature is null) return false;

        feature.DisplayNameFa = request.DisplayNameFa.Trim();
        feature.DisplayNameEn = request.DisplayNameEn?.Trim();
        feature.ModuleGroup = request.ModuleGroup.Trim();
        feature.IsEnabledGlobally = request.IsEnabledGlobally;
        feature.UpdatedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(cancellationToken);
        _catalog.InvalidateCache();
        return true;
    }

    public async Task<IReadOnlyList<AdminCafeListItemDto>> ListCafesAsync(CancellationToken cancellationToken = default)
    {
        return await _db.Cafes
            .AsNoTracking()
            .OrderByDescending(c => c.CreatedAt)
            .Select(c => new AdminCafeListItemDto(
                c.Id,
                c.Name,
                c.Slug,
                c.City ?? "",
                c.PlanTier,
                c.PlanExpiresAt,
                c.IsSuspended,
                c.IsVerified,
                c.Branches.Count,
                c.Employees.Count(e => e.DeletedAt == null),
                c.CreatedAt))
            .ToListAsync(cancellationToken);
    }

    public async Task<bool> PatchCafeAsync(string cafeId, AdminCafePatchRequest request, CancellationToken cancellationToken = default)
    {
        var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, cancellationToken);
        if (cafe is null) return false;

        if (request.PlanTier.HasValue)
            cafe.PlanTier = request.PlanTier.Value;
        if (request.PlanExpiresAt.HasValue)
            cafe.PlanExpiresAt = request.PlanExpiresAt;
        if (request.IsSuspended.HasValue)
            cafe.IsSuspended = request.IsSuspended.Value;
        if (request.IsVerified.HasValue)
            cafe.IsVerified = request.IsVerified.Value;
        if (request.DiscoverBadges is not null)
            cafe.DiscoverBadgesJson = DiscoverBadgesSerializer.Serialize(request.DiscoverBadges);

        await _db.SaveChangesAsync(cancellationToken);
        return true;
    }

    public async Task<bool> SetCafeFeatureOverrideAsync(
        string cafeId,
        CafeFeatureOverrideRequest request,
        CancellationToken cancellationToken = default)
    {
        var exists = await _db.Cafes.AnyAsync(c => c.Id == cafeId, cancellationToken);
        if (!exists) return false;

        var row = await _db.CafeFeatureOverrides
            .FirstOrDefaultAsync(o => o.CafeId == cafeId && o.FeatureKey == request.FeatureKey, cancellationToken);

        if (row is null)
        {
            row = new CafeFeatureOverride { CafeId = cafeId, FeatureKey = request.FeatureKey };
            _db.CafeFeatureOverrides.Add(row);
        }

        row.IsEnabled = request.IsEnabled;
        await _db.SaveChangesAsync(cancellationToken);
        return true;
    }

    public async Task<AdminCafeDiscoverProfileDto?> GetCafeDiscoverProfileAsync(
        string cafeId,
        CancellationToken cancellationToken = default)
    {
        var cafe = await _db.Cafes.AsNoTracking()
            .FirstOrDefaultAsync(c => c.Id == cafeId, cancellationToken);
        if (cafe is null) return null;

        var profile = CafeDiscoverProfileSerializer.Deserialize(cafe.DiscoverProfileJson);
        return MapAdminDiscoverProfile(cafe.Id, cafe.Name, profile);
    }

    public async Task<AdminCafeDiscoverProfileDto?> UpsertCafeDiscoverProfileAsync(
        string cafeId,
        AdminUpsertCafeDiscoverProfileRequest request,
        CancellationToken cancellationToken = default)
    {
        var cafe = await _db.Cafes.FirstOrDefaultAsync(c => c.Id == cafeId, cancellationToken);
        if (cafe is null) return null;

        var profile = CafeDiscoverProfileSerializer.Sanitize(new CafeDiscoverProfile
        {
            Themes = request.Themes?.ToList() ?? [],
            Size = request.Size,
            Floors = request.Floors,
            Vibes = request.Vibes?.ToList() ?? [],
            Occasions = request.Occasions?.ToList() ?? [],
            SpaceFeatures = request.SpaceFeatures?.ToList() ?? [],
            NoiseLevel = request.NoiseLevel,
            PriceTier = request.PriceTier
        });

        cafe.DiscoverProfileJson = CafeDiscoverProfileSerializer.Serialize(profile);
        await _db.SaveChangesAsync(cancellationToken);
        return MapAdminDiscoverProfile(cafe.Id, cafe.Name, profile);
    }

    private static AdminCafeDiscoverProfileDto MapAdminDiscoverProfile(
        string cafeId,
        string cafeName,
        CafeDiscoverProfile profile) =>
        new(
            cafeId,
            cafeName,
            profile.Themes,
            profile.Size,
            profile.Floors,
            profile.Vibes,
            profile.Occasions,
            profile.SpaceFeatures,
            profile.NoiseLevel,
            profile.PriceTier);
}
