using FlatRender.ContentSvc.Domain.Entities;
using FlatRender.ContentSvc.Domain.Enums;
using FlatRender.ContentSvc.Infrastructure.Data;
using FlatRender.ContentSvc.Models.Requests;
using FlatRender.ContentSvc.Models.Responses;
using Microsoft.EntityFrameworkCore;

namespace FlatRender.ContentSvc.Application.Services;

public class CmsService(ContentDbContext db)
{
    // ── Blogs ─────────────────────────────────────────────────────────────────

    public async Task<PagedResponse<BlogSummaryResponse>> GetBlogsAsync(BlogListRequest req)
    {
        var kind = Enum.TryParse<BlogKind>(req.Kind, true, out var k) ? k : BlogKind.Blog;
        var q = db.Blogs.Where(x => x.Kind == kind);

        if (!string.IsNullOrWhiteSpace(req.Search))
            q = q.Where(x => EF.Functions.ILike(x.Title, $"%{req.Search}%"));

        if (req.IsPublished.HasValue)
            q = q.Where(x => x.IsPublished == req.IsPublished);

        var total = await q.LongCountAsync();
        var items = await q.OrderByDescending(x => x.PublishDate ?? x.CreatedAt)
            .Skip((req.Page - 1) * req.PageSize).Take(req.PageSize).ToListAsync();

        return new PagedResponse<BlogSummaryResponse>(
            items.Select(MapBlogSummary),
            new PaginationMeta(req.Page, req.PageSize, total, (int)Math.Ceiling((double)total / req.PageSize))
        );
    }

    public async Task<BlogDetailResponse> GetBlogBySlugAsync(string slug)
    {
        var blog = await db.Blogs.FirstOrDefaultAsync(x => x.Slug == slug)
            ?? throw new KeyNotFoundException($"Blog '{slug}' not found");
        await db.Blogs.Where(x => x.Id == blog.Id)
            .ExecuteUpdateAsync(s => s.SetProperty(x => x.ViewCount, x => x.ViewCount + 1));
        return MapBlogDetail(blog);
    }

    public async Task<BlogDetailResponse> CreateBlogAsync(BlogListRequest _, CreateBlogRequest req, Guid? authorId)
    {
        var kind = Enum.TryParse<BlogKind>(req.Kind, true, out var k) ? k : BlogKind.Blog;
        var blog = new Blog
        {
            TenantId = null, Kind = kind, Slug = req.Slug, Title = req.Title,
            ShortDescription = req.ShortDescription, Content = req.Content,
            MetaTitle = req.MetaTitle, MetaDescription = req.MetaDescription, MetaKeywords = req.MetaKeywords,
            IncludeInSiteMap = req.IncludeInSiteMap, Image = req.Image, Cover = req.Cover,
            AuthorUserId = authorId, AuthorDisplayName = req.AuthorDisplayName,
            IsPublished = req.IsPublished, PublishDate = req.PublishDate
        };
        db.Blogs.Add(blog);
        await db.SaveChangesAsync();
        return MapBlogDetail(blog);
    }

    public async Task<BlogDetailResponse> UpdateBlogAsync(Guid id, UpdateBlogRequest req)
    {
        var blog = await db.Blogs.FindAsync(id)
            ?? throw new KeyNotFoundException($"Blog {id} not found");
        blog.Slug = req.Slug; blog.Title = req.Title; blog.ShortDescription = req.ShortDescription;
        blog.Content = req.Content; blog.MetaTitle = req.MetaTitle; blog.MetaDescription = req.MetaDescription;
        blog.MetaKeywords = req.MetaKeywords; blog.IncludeInSiteMap = req.IncludeInSiteMap;
        blog.Image = req.Image; blog.Cover = req.Cover; blog.AuthorDisplayName = req.AuthorDisplayName;
        blog.IsPublished = req.IsPublished; blog.PublishDate = req.PublishDate;
        blog.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
        return MapBlogDetail(blog);
    }

    public async Task DeleteBlogAsync(Guid id)
    {
        var blog = await db.Blogs.FindAsync(id) ?? throw new KeyNotFoundException($"Blog {id} not found");
        blog.DeletedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }

    // ── Comments ──────────────────────────────────────────────────────────────

    public async Task<PagedResponse<CommentResponse>> GetCommentsAsync(
        int page, int pageSize, Guid? blogId, Guid? containerId, bool? isApproved)
    {
        var q = db.Comments.AsQueryable();
        if (blogId.HasValue) q = q.Where(x => x.BlogId == blogId);
        if (containerId.HasValue) q = q.Where(x => x.ContainerId == containerId);
        if (isApproved.HasValue) q = q.Where(x => x.IsApproved == isApproved);

        var total = await q.LongCountAsync();
        var items = await q.OrderByDescending(x => x.CreatedAt).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();

        return new PagedResponse<CommentResponse>(
            items.Select(MapComment),
            new PaginationMeta(page, pageSize, total, (int)Math.Ceiling((double)total / pageSize))
        );
    }

    public async Task<CommentResponse> CreateCommentAsync(CreateCommentRequest req, Guid userId)
    {
        if (req.BlogId == null && req.ContainerId == null)
            throw new ArgumentException("Either BlogId or ContainerId must be provided");
        if (req.BlogId != null && req.ContainerId != null)
            throw new ArgumentException("Only one of BlogId or ContainerId may be provided");

        var comment = new Comment
        {
            UserId = userId, BlogId = req.BlogId, ContainerId = req.ContainerId,
            ParentCommentId = req.ParentCommentId, Content = req.Content, Rate = req.Rate
        };
        db.Comments.Add(comment);
        await db.SaveChangesAsync();
        return MapComment(comment);
    }

    public async Task ApproveCommentAsync(Guid id, bool approve)
    {
        var comment = await db.Comments.FindAsync(id)
            ?? throw new KeyNotFoundException($"Comment {id} not found");
        comment.IsApproved = approve;
        comment.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }

    public async Task DeleteCommentAsync(Guid id)
    {
        var comment = await db.Comments.FindAsync(id)
            ?? throw new KeyNotFoundException($"Comment {id} not found");
        comment.DeletedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }

    // ── Slides ────────────────────────────────────────────────────────────────

    public async Task<List<SlideResponse>> GetSlidesAsync(Guid? tenantId)
    {
        var q = db.NewSlides.Where(x => x.IsActive &&
            (x.TenantId == null || x.TenantId == tenantId) &&
            (x.ExpireDate == null || x.ExpireDate > DateTime.UtcNow));

        return await q.OrderBy(x => x.Sort).Select(s => new SlideResponse(
            s.Id, s.Keyword, s.Title, s.Image, s.Parameter, s.SlideType.ToString(),
            s.ExpireDate, s.Sort, s.IsActive
        )).ToListAsync();
    }

    public async Task<SlideResponse> CreateSlideAsync(CreateSlideRequest req)
    {
        if (!Enum.TryParse<SlideType>(req.SlideType, true, out var type))
            throw new ArgumentException($"Invalid SlideType: {req.SlideType}");

        var slide = new NewSlide
        {
            Keyword = req.Keyword, Title = req.Title, Image = req.Image,
            Parameter = req.Parameter, SlideType = type, ExpireDate = req.ExpireDate,
            Sort = req.Sort, IsActive = req.IsActive
        };
        db.NewSlides.Add(slide);
        await db.SaveChangesAsync();
        return new SlideResponse(slide.Id, slide.Keyword, slide.Title, slide.Image, slide.Parameter,
            slide.SlideType.ToString(), slide.ExpireDate, slide.Sort, slide.IsActive);
    }

    public async Task DeleteSlideAsync(Guid id)
    {
        var slide = await db.NewSlides.FindAsync(id) ?? throw new KeyNotFoundException($"Slide {id} not found");
        db.NewSlides.Remove(slide);
        await db.SaveChangesAsync();
    }

    // ── Home Page Events ──────────────────────────────────────────────────────

    public async Task<List<HomePageEventResponse>> GetHomePageEventsAsync(Guid? tenantId, bool includeInactive = false)
    {
        var q = db.HomePageEvents.Where(x => x.TenantId == null || x.TenantId == tenantId);
        if (!includeInactive) q = q.Where(x => x.IsActive);
        return await q.OrderBy(x => x.Sort).Select(e => MapHomeEvent(e)).ToListAsync();
    }

    private static HomePageEventResponse MapHomeEvent(HomePageEvent e) => new(
        e.Id, e.Title, e.Subtitle, e.Description, e.Badge, e.BadgeClass,
        e.ButtonText, e.ButtonUrl, e.ButtonClass, e.Color, e.BackgroundColor,
        e.TextColor, e.Image, e.IsActive, e.Sort, e.StartsAt, e.EndsAt);

    private static DateTime? Utc(DateTime? d) => d.HasValue ? DateTime.SpecifyKind(d.Value, DateTimeKind.Utc) : null;

    public async Task<HomePageEventResponse> CreateHomeEventAsync(UpsertHomeEventRequest req)
    {
        var e = new HomePageEvent();
        ApplyHomeEvent(e, req);
        db.HomePageEvents.Add(e);
        await db.SaveChangesAsync();
        return MapHomeEvent(e);
    }

    public async Task<HomePageEventResponse> UpdateHomeEventAsync(Guid id, UpsertHomeEventRequest req)
    {
        var e = await db.HomePageEvents.FindAsync(id)
            ?? throw new KeyNotFoundException($"Home event {id} not found");
        ApplyHomeEvent(e, req);
        e.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
        return MapHomeEvent(e);
    }

    public async Task DeleteHomeEventAsync(Guid id)
    {
        var e = await db.HomePageEvents.FindAsync(id)
            ?? throw new KeyNotFoundException($"Home event {id} not found");
        db.HomePageEvents.Remove(e);
        await db.SaveChangesAsync();
    }

    private static void ApplyHomeEvent(HomePageEvent e, UpsertHomeEventRequest r)
    {
        e.Title = r.Title; e.Subtitle = r.Subtitle; e.Description = r.Description;
        e.Badge = r.Badge; e.BadgeClass = r.BadgeClass;
        e.ButtonText = r.ButtonText; e.ButtonUrl = r.ButtonUrl; e.ButtonClass = r.ButtonClass;
        e.Color = r.Color; e.BackgroundColor = r.BackgroundColor; e.TextColor = r.TextColor;
        e.Image = r.Image; e.IsActive = r.IsActive; e.Sort = r.Sort;
        e.StartsAt = Utc(r.StartsAt); e.EndsAt = Utc(r.EndsAt);
    }

    // ── Internal routes ─────────────────────────────────────────────────────────

    public async Task<List<InternalRouteResponse>> GetRoutesAsync(Guid? tenantId)
    {
        return await db.InternalRoutes
            .Where(x => x.TenantId == null || x.TenantId == tenantId)
            .OrderBy(x => x.Priority)
            .Select(r => new InternalRouteResponse(r.Id, r.Name, r.Image, r.Slug, r.Priority, r.LastDate))
            .ToListAsync();
    }

    public async Task<InternalRouteResponse> CreateRouteAsync(UpsertRouteRequest req)
    {
        var r = new InternalRoute { Name = req.Name, Image = req.Image, Slug = req.Slug, Priority = req.Priority, LastDate = Utc(req.LastDate) };
        db.InternalRoutes.Add(r);
        await db.SaveChangesAsync();
        return new InternalRouteResponse(r.Id, r.Name, r.Image, r.Slug, r.Priority, r.LastDate);
    }

    public async Task<InternalRouteResponse> UpdateRouteAsync(Guid id, UpsertRouteRequest req)
    {
        var r = await db.InternalRoutes.FindAsync(id) ?? throw new KeyNotFoundException($"Route {id} not found");
        r.Name = req.Name; r.Image = req.Image; r.Slug = req.Slug; r.Priority = req.Priority;
        r.LastDate = Utc(req.LastDate); r.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
        return new InternalRouteResponse(r.Id, r.Name, r.Image, r.Slug, r.Priority, r.LastDate);
    }

    public async Task DeleteRouteAsync(Guid id)
    {
        var r = await db.InternalRoutes.FindAsync(id) ?? throw new KeyNotFoundException($"Route {id} not found");
        db.InternalRoutes.Remove(r);
        await db.SaveChangesAsync();
    }

    // ── Website Settings ──────────────────────────────────────────────────────

    public async Task<List<WebsiteSettingResponse>> GetSettingsAsync(Guid? tenantId, bool includeSecret = false)
    {
        var q = db.WebsiteSettings.Where(x => x.TenantId == tenantId);
        if (!includeSecret) q = q.Where(x => !x.IsSecret);
        return await q.Select(s => new WebsiteSettingResponse(s.Id, s.Key, s.Value, s.Description, s.IsSecret)).ToListAsync();
    }

    public async Task<WebsiteSettingResponse> UpsertSettingAsync(Guid? tenantId, UpsertWebsiteSettingRequest req)
    {
        var setting = await db.WebsiteSettings.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Key == req.Key);
        if (setting == null)
        {
            setting = new WebsiteSetting { TenantId = tenantId, Key = req.Key };
            db.WebsiteSettings.Add(setting);
        }
        setting.Value = req.Value;
        setting.Description = req.Description;
        setting.IsSecret = req.IsSecret;
        setting.UpdatedAt = DateTime.UtcNow;
        await db.SaveChangesAsync();
        return new WebsiteSettingResponse(setting.Id, setting.Key, setting.Value, setting.Description, setting.IsSecret);
    }

    // ── Favorites ─────────────────────────────────────────────────────────────

    public async Task<List<FavoriteFolderResponse>> GetFavoriteFoldersAsync(Guid userId)
    {
        return await db.FavoriteFolders
            .Where(x => x.UserId == userId)
            .Select(f => new FavoriteFolderResponse(
                f.Id, f.Name, f.Description,
                db.FavoriteContainers.Count(fc => fc.FolderId == f.Id),
                f.CreatedAt
            )).ToListAsync();
    }

    public async Task<FavoriteFolderResponse> CreateFavoriteFolderAsync(Guid userId, Guid tenantId, CreateFavoriteFolderRequest req)
    {
        var folder = new FavoriteFolder { UserId = userId, TenantId = tenantId, Name = req.Name, Description = req.Description };
        db.FavoriteFolders.Add(folder);
        await db.SaveChangesAsync();
        return new FavoriteFolderResponse(folder.Id, folder.Name, folder.Description, 0, folder.CreatedAt);
    }

    public async Task DeleteFavoriteFolderAsync(Guid userId, Guid id)
    {
        var folder = await db.FavoriteFolders.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId)
            ?? throw new KeyNotFoundException($"Folder {id} not found");
        db.FavoriteFolders.Remove(folder);
        await db.SaveChangesAsync();
    }

    public async Task AddFavoriteContainerAsync(Guid userId, Guid tenantId, AddFavoriteContainerRequest req)
    {
        var exists = await db.FavoriteContainers.AnyAsync(x => x.UserId == userId && x.ContainerId == req.ContainerId);
        if (exists) return;

        db.FavoriteContainers.Add(new FavoriteContainer
        {
            UserId = userId, TenantId = tenantId, ContainerId = req.ContainerId,
            FolderId = req.FolderId, Note = req.Note
        });
        await db.SaveChangesAsync();
    }

    public async Task RemoveFavoriteContainerAsync(Guid userId, Guid containerId)
    {
        var fav = await db.FavoriteContainers.FirstOrDefaultAsync(x => x.UserId == userId && x.ContainerId == containerId)
            ?? throw new KeyNotFoundException($"Favorite not found");
        db.FavoriteContainers.Remove(fav);
        await db.SaveChangesAsync();
    }

    // ── Mappers ───────────────────────────────────────────────────────────────

    private static BlogSummaryResponse MapBlogSummary(Blog b) => new(
        b.Id, b.Slug, b.Title, b.ShortDescription, b.Image, b.Cover,
        b.AuthorDisplayName, b.IsPublished, b.PublishDate, b.ViewCount, b.CreatedAt
    );

    private static BlogDetailResponse MapBlogDetail(Blog b) => new(
        b.Id, b.Slug, b.Title, b.ShortDescription, b.Content, b.MetaTitle, b.MetaDescription,
        b.MetaKeywords, b.IncludeInSiteMap, b.Image, b.Cover, b.AuthorUserId, b.AuthorDisplayName,
        b.IsPublished, b.PublishDate, b.ViewCount, b.CreatedAt, b.UpdatedAt
    );

    private static CommentResponse MapComment(Comment c) => new(
        c.Id, c.UserId, c.BlogId, c.ContainerId, c.ParentCommentId,
        c.Content, c.Rate, c.IsApproved, c.IsPinned, c.CreatedAt
    );
}
