using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using NodaTime;

namespace DysonNetwork.Sphere.Publisher;

public class PublisherService(AppDatabase db, FileService fs, FileReferenceService fileRefService, ICacheService cache)
{
    private const string UserPublishersCacheKey = "accounts:{0}:publishers";

    public async Task<List<Publisher>> GetUserPublishers(Guid userId)
    {
        var cacheKey = string.Format(UserPublishersCacheKey, userId);
        
        // Try to get publishers from the cache first
        var publishers = await cache.GetAsync<List<Publisher>>(cacheKey);
        if (publishers is not null)
            return publishers;
        
        // If not in cache, fetch from a database
        var publishersId = await db.PublisherMembers
            .Where(p => p.AccountId == userId)
            .Select(p => p.PublisherId)
            .ToListAsync();
        publishers = await db.Publishers
            .Where(p => publishersId.Contains(p.Id))
            .ToListAsync();
        
        // Store in a cache for 5 minutes
        await cache.SetAsync(cacheKey, publishers, TimeSpan.FromMinutes(5));
        
        return publishers;
    }

    private const string PublisherMembersCacheKey = "publishers:{0}:members";

    public async Task<List<PublisherMember>> GetPublisherMembers(Guid publisherId)
    {
        var cacheKey = string.Format(PublisherMembersCacheKey, publisherId);
        
        // Try to get members from the cache first
        var members = await cache.GetAsync<List<PublisherMember>>(cacheKey);
        if (members is not null)
            return members;
        
        // If not in cache, fetch from a database
        members = await db.PublisherMembers
            .Where(p => p.PublisherId == publisherId)
            .Include(p => p.Account)
            .ThenInclude(p => p.Profile)
            .ToListAsync();
        
        // Store in cache for 5 minutes (consistent with other cache durations in the class)
        await cache.SetAsync(cacheKey, members, TimeSpan.FromMinutes(5));
        
        return members;
    }
    
    public async Task<Publisher> CreateIndividualPublisher(
        Account.Account account,
        string? name,
        string? nick,
        string? bio,
        CloudFile? picture,
        CloudFile? background
    )
    {
        var publisher = new Publisher
        {
            Type = PublisherType.Individual,
            Name = name ?? account.Name,
            Nick = nick ?? account.Nick,
            Bio = bio ?? account.Profile.Bio,
            Picture = picture?.ToReferenceObject() ?? account.Profile.Picture,
            Background = background?.ToReferenceObject() ?? account.Profile.Background,
            AccountId = account.Id,
            Members = new List<PublisherMember>
            {
                new()
                {
                    AccountId = account.Id,
                    Role = PublisherMemberRole.Owner,
                    JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow)
                }
            }
        };

        db.Publishers.Add(publisher);
        await db.SaveChangesAsync();

        var publisherResourceId = $"publisher:{publisher.Id}";

        if (publisher.Picture is not null) {
            await fileRefService.CreateReferenceAsync(
                publisher.Picture.Id,
                "publisher.picture",
                publisherResourceId
            );
        }

        if (publisher.Background is not null) {
            await fileRefService.CreateReferenceAsync(
                publisher.Background.Id,
                "publisher.background",
                publisherResourceId
            );
        }

        return publisher;
    }

    public async Task<Publisher> CreateOrganizationPublisher(
        Realm.Realm realm,
        Account.Account account,
        string? name,
        string? nick,
        string? bio,
        CloudFile? picture,
        CloudFile? background
    )
    {
        var publisher = new Publisher
        {
            Type = PublisherType.Organizational,
            Name = name ?? realm.Slug,
            Nick = nick ?? realm.Name,
            Bio = bio ?? realm.Description,
            Picture = picture?.ToReferenceObject() ?? realm.Picture,
            Background = background?.ToReferenceObject() ?? realm.Background,
            RealmId = realm.Id,
            Members = new List<PublisherMember>
            {
                new()
                {
                    AccountId = account.Id,
                    Role = PublisherMemberRole.Owner,
                    JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow)
                }
            }
        };

        db.Publishers.Add(publisher);
        await db.SaveChangesAsync();

        var publisherResourceId = $"publisher:{publisher.Id}";

        if (publisher.Picture is not null) {
            await fileRefService.CreateReferenceAsync(
                publisher.Picture.Id,
                "publisher.picture",
                publisherResourceId
            );
        }

        if (publisher.Background is not null) {
            await fileRefService.CreateReferenceAsync(
                publisher.Background.Id,
                "publisher.background",
                publisherResourceId
            );
        }

        return publisher;
    }

    public class PublisherStats
    {
        public int PostsCreated { get; set; }
        public int StickerPacksCreated { get; set; }
        public int StickersCreated { get; set; }
        public int UpvoteReceived { get; set; }
        public int DownvoteReceived { get; set; }
        public int SubscribersCount { get; set; }
    }

    private const string PublisherStatsCacheKey = "PublisherStats_{0}";
    private const string PublisherFeatureCacheKey = "PublisherFeature_{0}_{1}";

    public async Task<PublisherStats?> GetPublisherStats(string name)
    {
        var cacheKey = string.Format(PublisherStatsCacheKey, name);
        var stats = await cache.GetAsync<PublisherStats>(cacheKey);
        if (stats is not null)
            return stats;

        var publisher = await db.Publishers.FirstOrDefaultAsync(e => e.Name == name);
        if (publisher is null) return null;

        var postsCount = await db.Posts.Where(e => e.Publisher.Id == publisher.Id).CountAsync();
        var postsUpvotes = await db.PostReactions
            .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Positive)
            .CountAsync();
        var postsDownvotes = await db.PostReactions
            .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Negative)
            .CountAsync();

        var stickerPacksId = await db.StickerPacks.Where(e => e.Publisher.Id == publisher.Id).Select(e => e.Id)
            .ToListAsync();
        var stickerPacksCount = stickerPacksId.Count;

        var stickersCount = await db.Stickers.Where(e => stickerPacksId.Contains(e.PackId)).CountAsync();

        var subscribersCount = await db.PublisherSubscriptions.Where(e => e.PublisherId == publisher.Id).CountAsync();

        stats = new PublisherStats
        {
            PostsCreated = postsCount,
            StickerPacksCreated = stickerPacksCount,
            StickersCreated = stickersCount,
            UpvoteReceived = postsUpvotes,
            DownvoteReceived = postsDownvotes,
            SubscribersCount = subscribersCount,
        };

        await cache.SetAsync(cacheKey, stats, TimeSpan.FromMinutes(5));
        return stats;
    }

    public async Task SetFeatureFlag(Guid publisherId, string flag)
    {
        var featureFlag = await db.PublisherFeatures
            .FirstOrDefaultAsync(f => f.PublisherId == publisherId && f.Flag == flag);

        if (featureFlag == null)
        {
            featureFlag = new PublisherFeature
            {
                PublisherId = publisherId,
                Flag = flag,
            };
            db.PublisherFeatures.Add(featureFlag);
        }
        else
        {
            featureFlag.ExpiredAt = SystemClock.Instance.GetCurrentInstant();
        }

        await db.SaveChangesAsync();
        await cache.RemoveAsync(string.Format(PublisherFeatureCacheKey, publisherId, flag));
    }

    public async Task<bool> HasFeature(Guid publisherId, string flag)
    {
        var cacheKey = string.Format(PublisherFeatureCacheKey, publisherId, flag);

        var isEnabled = await cache.GetAsync<bool?>(cacheKey);
        if (isEnabled.HasValue)
            return isEnabled.Value;

        var now = SystemClock.Instance.GetCurrentInstant();
        var featureFlag = await db.PublisherFeatures
            .FirstOrDefaultAsync(f =>
                f.PublisherId == publisherId && f.Flag == flag &&
                (f.ExpiredAt == null || f.ExpiredAt > now)
            );
        if (featureFlag is not null) isEnabled = true;

        await cache.SetAsync(cacheKey, isEnabled!.Value, TimeSpan.FromMinutes(5));
        return isEnabled.Value;
    }

    public async Task<bool> IsMemberWithRole(Guid publisherId, Guid accountId, PublisherMemberRole requiredRole)
    {
        var member = await db.Publishers
            .Where(p => p.Id == publisherId)
            .SelectMany(p => p.Members)
            .FirstOrDefaultAsync(m => m.AccountId == accountId);

        return member != null && member.Role >= requiredRole;
    }
}