namespace TaxBaik.Application.Services;

using System.Text.RegularExpressions;
using FluentValidation;
using TaxBaik.Application.DTOs;
using TaxBaik.Domain.Entities;
using TaxBaik.Domain.Interfaces;

using Microsoft.Extensions.Caching.Memory;

public class BlogService(
    IBlogPostRepository repository,
    ICategoryRepository categoryRepository,
    IMemoryCache memoryCache,
    IValidator<CreateBlogPostDto> validator)
{
    public async Task<BlogPost?> GetByIdAsync(int id, CancellationToken ct = default) =>
        await repository.GetByIdAsync(id, ct);

    public async Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken ct = default) =>
        await repository.GetBySlugAsync(slug, ct);

    /// <summary>카테고리 슬러그 기준 시즌 관련 글 조회. 부족 시 최신 글로 채워 total개 반환.</summary>
    public async Task<(IEnumerable<BlogPost> Seasonal, IEnumerable<BlogPost> Latest)> GetSeasonalPostsAsync(
        string categorySlug, int seasonalCount, int totalCount, CancellationToken ct = default)
    {
        var seasonal = (await repository.GetByCategorySlugAsync(categorySlug, seasonalCount, ct)).ToList();
        var seasonalIds = seasonal.Select(p => p.Id).ToHashSet();

        var (latestAll, _) = await repository.GetPublishedPagedAsync(1, totalCount + seasonalCount, null, ct);
        var latest = latestAll.Where(p => !seasonalIds.Contains(p.Id)).Take(totalCount - seasonal.Count).ToList();

        return (seasonal, latest);
    }

    public async Task<(IEnumerable<BlogPost>, int)> GetPublishedPagedAsync(
        int page, int pageSize, int? categoryId = null, CancellationToken ct = default) =>
        await repository.GetPublishedPagedAsync(NormalizePage(page), NormalizePageSize(pageSize), categoryId, ct);

    public async Task<IEnumerable<BlogPost>> GetAllAsync(CancellationToken ct = default) =>
        await repository.GetAllForAdminAsync(ct);

    public async Task<IEnumerable<BlogPost>> GetAllForAdminAsync(CancellationToken ct = default) =>
        await repository.GetAllForAdminAsync(ct);

    public async Task<(IEnumerable<BlogPost>, int)> GetAdminPagedAsync(
        int page, int pageSize, CancellationToken ct = default) =>
        await repository.GetAdminPagedAsync(NormalizePage(page), NormalizePageSize(pageSize), ct);

    public async Task<(IEnumerable<BlogPost>, int)> GetArchivedPagedAsync(
        int page, int pageSize, CancellationToken ct = default) =>
        await repository.GetArchivedPagedAsync(NormalizePage(page), NormalizePageSize(pageSize), ct);

    public async Task<int> CreateAsync(BlogPost post, CancellationToken ct = default)
    {
        ValidatePost(post);
        post.CategoryId ??= await EnsureCategoryIdAsync(0, ct);
        post.Title = post.Title.Trim();
        post.Content = post.Content.Trim();
        post.Slug = await GenerateUniqueSlugAsync(post.Title, ct: ct);
        post.PublishedAt = post.IsPublished ? DateTime.UtcNow : null;
        var result = await repository.CreateAsync(post, ct);
        memoryCache.Remove(AdminDashboardService.CacheKey);
        return result;
    }

    public async Task<BlogPost> CreateAsync(CreateBlogPostDto dto, CancellationToken ct = default)
    {
        validator.ValidateAndThrow(dto);
        dto.CategoryId = await EnsureCategoryIdAsync(dto.CategoryId, ct);
        var post = new BlogPost
        {
            Title = dto.Title,
            Content = dto.Content,
            CategoryId = dto.CategoryId,
            Tags = dto.Tags,
            SeoTitle = dto.SeoTitle,
            SeoDescription = dto.SeoDescription,
            ThumbnailUrl = dto.ThumbnailUrl,
            IsPublished = dto.IsPublished,
            AuthorId = dto.AuthorId,
            CreatedAt = DateTime.UtcNow
        };

        var id = await CreateAsync(post, ct);
        post.Id = id;
        return post;
    }

    public async Task UpdateAsync(BlogPost post, CancellationToken ct = default)
    {
        post.CategoryId ??= await EnsureCategoryIdAsync(0, ct);
        await repository.UpdateAsync(post, ct);
        memoryCache.Remove(AdminDashboardService.CacheKey);
    }

    public async Task<BlogPost?> UpdateAsync(int id, CreateBlogPostDto dto, CancellationToken ct = default)
    {
        validator.ValidateAndThrow(dto);
        dto.CategoryId = await EnsureCategoryIdAsync(dto.CategoryId, ct);
        var post = await repository.GetByIdAsync(id, ct);
        if (post == null)
            return null;

        post.Title = dto.Title;
        post.Content = dto.Content;
        post.CategoryId = dto.CategoryId;
        post.Tags = dto.Tags;
        post.SeoTitle = dto.SeoTitle;
        post.SeoDescription = dto.SeoDescription;
        post.ThumbnailUrl = dto.ThumbnailUrl;
        post.IsPublished = dto.IsPublished;
        post.PublishedAt = dto.IsPublished
            ? post.PublishedAt ?? DateTime.UtcNow
            : null;
        ValidatePost(post);

        await UpdateAsync(post, ct);
        return post;
    }

    public async Task DeleteAsync(int id, CancellationToken ct = default)
    {
        await repository.DeleteAsync(id, ct);
        memoryCache.Remove(AdminDashboardService.CacheKey);
    }

    public async Task ArchiveAsync(int id, CancellationToken ct = default)
    {
        await repository.ArchiveAsync(id, ct);
        memoryCache.Remove(AdminDashboardService.CacheKey);
    }

    public async Task RestoreAsync(int id, CancellationToken ct = default)
    {
        await repository.RestoreAsync(id, ct);
        memoryCache.Remove(AdminDashboardService.CacheKey);
    }

    public async Task IncrementViewCountAsync(int id, CancellationToken ct = default) =>
        await repository.IncrementViewCountAsync(id, ct);

    private static string GenerateSlug(string title)
    {
        var slug = Regex.Replace(title.ToLowerInvariant(), @"[^\w\s-]", "");
        slug = Regex.Replace(slug, @"\s+", "-");
        slug = Regex.Replace(slug, @"-+", "-").Trim('-');
        if (string.IsNullOrWhiteSpace(slug))
            slug = $"post-{DateTime.UtcNow:yyyyMMddHHmmss}";
        return slug.Length > 100 ? slug[..100] : slug;
    }

    private async Task<string> GenerateUniqueSlugAsync(string title, int? existingPostId = null, CancellationToken ct = default)
    {
        var baseSlug = GenerateSlug(title);
        var slug = baseSlug;
        var suffix = 2;
        var allPosts = (await repository.GetAllForAdminAsync(ct)).ToList();

        while (allPosts.Any(x => x.Id != existingPostId && string.Equals(x.Slug, slug, StringComparison.OrdinalIgnoreCase)))
        {
            var suffixText = $"-{suffix++}";
            var maxBaseLength = Math.Max(1, 100 - suffixText.Length);
            slug = $"{baseSlug[..Math.Min(baseSlug.Length, maxBaseLength)]}{suffixText}";
        }

        return slug;
    }

    private static void ValidatePost(BlogPost post)
    {
        if (string.IsNullOrWhiteSpace(post.Title))
            throw new ValidationException("제목을 입력하세요.");

        if (string.IsNullOrWhiteSpace(post.Content))
            throw new ValidationException("본문을 입력하세요.");

        if (post.IsPublished && string.IsNullOrWhiteSpace(post.SeoTitle))
            throw new ValidationException("발행하려면 SEO 제목을 입력하세요.");

        if (post.IsPublished && string.IsNullOrWhiteSpace(post.SeoDescription))
            throw new ValidationException("발행하려면 SEO 설명을 입력하세요.");
    }

    private static int NormalizePage(int page) => Math.Max(1, page);

    private static int NormalizePageSize(int pageSize) => Math.Clamp(pageSize, 1, 100);

    private async Task<int> EnsureCategoryIdAsync(int categoryId, CancellationToken ct)
    {
        if (categoryId > 0)
            return categoryId;

        var firstCategory = (await categoryRepository.GetAllAsync(ct)).FirstOrDefault();
        return firstCategory?.Id ?? throw new ValidationException("블로그 카테고리를 먼저 생성하세요.");
    }

    public async Task<(int TotalPosts, int PublishedPosts)> GetStatsAsync(CancellationToken ct = default)
    {
        var posts = (await repository.GetAllForAdminAsync(ct)).ToList();
        return (posts.Count, posts.Count(x => x.IsPublished));
    }
}
