using Application.Abstractions.Messaging;
using Application.Abstractions.Data;
using SharedKernel.Storage;
using Microsoft.EntityFrameworkCore;
using System.Text.RegularExpressions;

namespace Application.Features.Admin.Forum.Post.Update;

public sealed class Handler(IAppDbContext db, IFileStorage fileStorage) : ICommandHandler<Command>
{
    private static readonly string[] AllowedImageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
    private static readonly string[] AllowedFileExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".zip", ".rar", ".7z", ".hwp", ".hwpx", ".csv"];
    private static readonly Regex ImgBase64Regex = new(@"<img\b[^>]*?\bsrc\s*=\s*""(?<src>data:image/(?<ext>[a-zA-Z0-9.+-]+);base64,(?<data>[a-zA-Z0-9+/=\s]+))""", RegexOptions.Compiled | RegexOptions.IgnoreCase);

    public async Task Handle(Command request, CancellationToken ct)
    {
        var post = await db.Post.FirstOrDefaultAsync(x => x.ID == request.ID, ct);
        if (post is null)
        {
            throw new KeyNotFoundException("게시글을 찾을 수 없습니다.");
        }

        post.BoardPrefixID = request.BoardPrefixID;
        post.Subject = request.Subject;
        post.Content = request.Content ?? string.Empty;
        post.IsNotice = request.IsNotice;
        post.IsSecret = request.IsSecret;
        post.IsAnonymous = request.IsAnonymous;
        post.UpdatedAt = DateTime.UtcNow;

        var uploadPath = new FileStoragePath(UploadTarget.Upload, UploadFolder.Post, post.ID);

        // 에디터 이미지 처리 (base64 → 파일 + PostImage 레코드)
        var content = post.Content;
        var matches = ImgBase64Regex.Matches(content);

        if (matches.Count > 0)
        {
            byte newImageCount = 0;
            var firstImageUrl = (string?)null;

            foreach (Match match in matches)
            {
                var ext = FileUtils.NormalizeExtension(match.Groups["ext"].Value);
                var bytes = Convert.FromBase64String(match.Groups["data"].Value);
                var result = await fileStorage.SaveBytesAsync(bytes, ext, uploadPath, ct);

                if (result is not null)
                {
                    content = content.Replace(match.Groups["src"].Value, result.Url);
                    firstImageUrl ??= result.Url;

                    await db.PostImage.AddAsync(new Domain.Entities.Forum.Posts.PostImage
                    {
                        BoardID = post.BoardID,
                        PostID = post.ID,
                        FileName = result.FileName,
                        HashedName = result.FileName,
                        Path = uploadPath.ToRelativePath(),
                        Url = result.Url,
                        Extension = ext,
                        ContentType = $"image/{match.Groups["ext"].Value}",
                        Size = result.Size,
                        Width = result.Width,
                        Height = result.Height
                    }, ct);

                    newImageCount++;
                }
            }

            post.Content = content;

            var existingImageCount = await db.PostImage.CountAsync(x => x.PostID == post.ID && !x.IsDisabled, ct);
            post.Images = (byte)Math.Min(existingImageCount + newImageCount, byte.MaxValue);

            if (string.IsNullOrEmpty(post.Thumbnail) && firstImageUrl is not null)
            {
                post.Thumbnail = firstImageUrl;
            }
        }

        // 썸네일 처리
        if (request.ThumbnailFile is not null)
        {
            if (!string.IsNullOrEmpty(post.Thumbnail))
            {
                fileStorage.DeleteByUrl(post.Thumbnail);
            }

            var result = await fileStorage.SaveFileAsync(request.ThumbnailFile, uploadPath, AllowedImageExtensions, ct);
            post.Thumbnail = result?.Url;
        }

        // 파일 처리 (새 파일 추가)
        if (request.Files is { Count: > 0 })
        {
            foreach (var file in request.Files)
            {
                var result = await fileStorage.SaveFileAsync(file, uploadPath, AllowedFileExtensions, ct);
                if (result is not null)
                {
                    var ext = Path.GetExtension(file.FileName).ToLowerInvariant();

                    await db.PostFile.AddAsync(new Domain.Entities.Forum.Posts.PostFile
                    {
                        BoardID = post.BoardID,
                        PostID = post.ID,
                        UUID = Guid.NewGuid(),
                        FileName = file.FileName,
                        HashedName = result.FileName,
                        Path = uploadPath.ToRelativePath(),
                        Url = result.Url,
                        Extension = ext,
                        ContentType = file.ContentType,
                        Size = result.Size
                    }, ct);
                }
            }

            var fileCount = await db.PostFile.CountAsync(x => x.PostID == post.ID && !x.IsDisabled, ct);
            post.Files = (byte)Math.Min(fileCount + request.Files.Count, byte.MaxValue);
        }

        // 태그 처리 (diff 기반)
        if (request.Tags is not null)
        {
            var existingPostTags = await db.PostTag.Include(x => x.Tag).Where(x => x.PostID == post.ID).ToListAsync(ct);
            var newTagSlugs = request.Tags.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t.Trim().ToLowerInvariant().Replace(' ', '-')).ToHashSet();
            var existingSlugs = existingPostTags.Select(x => x.Tag.Slug).ToHashSet();

            // 삭제할 태그: 기존에 있지만 새 목록에 없는 것
            var toRemove = existingPostTags.Where(x => !newTagSlugs.Contains(x.Tag.Slug)).ToList();

            foreach (var postTag in toRemove)
            {
                db.PostTag.Remove(postTag);

                if (postTag.Tag.UsageCount > 0)
                {
                    postTag.Tag.UsageCount--;
                    postTag.Tag.UpdatedAt = DateTime.UtcNow;
                }
            }

            // 추가할 태그: 새 목록에 있지만 기존에 없는 것
            var toAdd = request.Tags
                .Where(t => !string.IsNullOrWhiteSpace(t))
                .Select(t => t.Trim())
                .Where(t => !existingSlugs.Contains(t.ToLowerInvariant().Replace(' ', '-')))
                .ToList();

            foreach (var tagName in toAdd)
            {
                var slug = tagName.ToLowerInvariant().Replace(' ', '-');

                var tag = await db.Tag.FirstOrDefaultAsync(x => x.Slug == slug, ct);
                if (tag is null)
                {
                    tag = new Domain.Entities.Forum.Posts.Tag
                    {
                        Name = tagName,
                        Slug = slug
                    };

                    await db.Tag.AddAsync(tag, ct);
                    await db.SaveChangesAsync(ct);
                }

                tag.UsageCount++;
                tag.UpdatedAt = DateTime.UtcNow;

                await db.PostTag.AddAsync(new Domain.Entities.Forum.Posts.PostTag
                {
                    BoardID = post.BoardID,
                    PostID = post.ID,
                    TagID = tag.ID
                }, ct);
            }

            post.Tags = (byte)Math.Min(newTagSlugs.Count, byte.MaxValue);
        }

        await db.SaveChangesAsync(ct);
    }
}