namespace TaxBaik.Application.Tests;

using FluentValidation;
using TaxBaik.Application.DTOs;
using TaxBaik.Application.Services;
using TaxBaik.Domain.Entities;
using TaxBaik.Domain.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Xunit;

public class BlogServiceTests
{
    [Fact]
    public async Task CreateAsync_WhenPublishedWithoutSeoTitle_ThrowsValidationException()
    {
        var service = new BlogService(
            new FakeBlogPostRepository(),
            new FakeCategoryRepository(),
            new MemoryCache(new MemoryCacheOptions()),
            new PassThroughValidator<CreateBlogPostDto>());

        await Assert.ThrowsAsync<TaxBaik.Application.Services.ValidationException>(() => service.CreateAsync(new CreateBlogPostDto
        {
            Title = "테스트 포스트",
            Content = "본문",
            SeoDescription = "설명",
            IsPublished = true
        }));
    }

    [Fact]
    public async Task CreateAsync_WhenTitleDuplicates_GeneratesUniqueSlug()
    {
        var repository = new FakeBlogPostRepository
        {
            Posts =
            [
                new BlogPost { Id = 1, Title = "같은 제목", Content = "본문", Slug = "같은-제목" }
            ]
        };
        var service = new BlogService(
            repository,
            new FakeCategoryRepository(),
            new MemoryCache(new MemoryCacheOptions()),
            new PassThroughValidator<CreateBlogPostDto>());

        var post = await service.CreateAsync(new CreateBlogPostDto
        {
            Title = "같은 제목",
            Content = "본문"
        });

        Assert.Equal("같은-제목-2", post.Slug);
    }

    [Fact]
    public async Task DeleteAsync_SoftDeletesPost_AndExcludesFromSlugLookup()
    {
        var repository = new FakeBlogPostRepository
        {
            Posts =
            [
                new BlogPost { Id = 1, Title = "삭제 대상", Content = "본문", Slug = "delete-me", IsPublished = true }
            ]
        };
        var service = new BlogService(
            repository,
            new FakeCategoryRepository(),
            new MemoryCache(new MemoryCacheOptions()),
            new PassThroughValidator<CreateBlogPostDto>());

        await service.DeleteAsync(1);

        Assert.NotNull(repository.Posts.Single().DeletedAt);
        Assert.Null(await service.GetBySlugAsync("delete-me"));
        Assert.Null(await service.GetByIdAsync(1));
    }

    private sealed class FakeBlogPostRepository : IBlogPostRepository
    {
        public List<BlogPost> Posts { get; init; } = [];

        public Task<BlogPost?> GetByIdAsync(int id, CancellationToken cancellationToken = default) =>
            Task.FromResult(Posts.FirstOrDefault(x => x.Id == id && x.DeletedAt == null));

        public Task<BlogPost?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default) =>
            Task.FromResult(Posts.FirstOrDefault(x => x.Slug == slug && x.IsPublished && x.DeletedAt == null));

        public Task<(IEnumerable<BlogPost> Items, int Total)> GetPublishedPagedAsync(
            int page, int pageSize, int? categoryId = null, CancellationToken cancellationToken = default)
        {
            var items = Posts.Where(x => x.IsPublished).ToList();
            return Task.FromResult<(IEnumerable<BlogPost>, int)>((items, items.Count));
        }

        public Task<IEnumerable<BlogPost>> GetByCategorySlugAsync(string categorySlug, int limit, CancellationToken cancellationToken = default) =>
            Task.FromResult<IEnumerable<BlogPost>>(Posts.Where(x => x.IsPublished).Take(limit).ToList());

        public Task<IEnumerable<BlogPost>> GetAllForAdminAsync(CancellationToken cancellationToken = default) =>
            Task.FromResult<IEnumerable<BlogPost>>(Posts);

        public Task<(IEnumerable<BlogPost> Items, int Total)> GetAdminPagedAsync(
            int page, int pageSize, CancellationToken cancellationToken = default)
        {
            var items = Posts.ToList();
            return Task.FromResult<(IEnumerable<BlogPost>, int)>((items, items.Count));
        }

        public Task<(IEnumerable<BlogPost> Items, int Total)> GetArchivedPagedAsync(
            int page, int pageSize, CancellationToken cancellationToken = default)
        {
            var items = Posts.Where(x => x.DeletedAt != null).ToList();
            return Task.FromResult<(IEnumerable<BlogPost>, int)>((items, items.Count));
        }

        public Task<int> CreateAsync(BlogPost post, CancellationToken cancellationToken = default)
        {
            post.Id = Posts.Count + 1;
            Posts.Add(post);
            return Task.FromResult(post.Id);
        }

        public Task UpdateAsync(BlogPost post, CancellationToken cancellationToken = default) => Task.CompletedTask;

        public Task DeleteAsync(int id, CancellationToken cancellationToken = default)
        {
            var post = Posts.FirstOrDefault(x => x.Id == id);
            if (post != null)
                post.DeletedAt = DateTime.UtcNow;
            return Task.CompletedTask;
        }

        public Task ArchiveAsync(int id, CancellationToken cancellationToken = default) => DeleteAsync(id, cancellationToken);

        public Task RestoreAsync(int id, CancellationToken cancellationToken = default)
        {
            var post = Posts.FirstOrDefault(x => x.Id == id);
            if (post != null)
                post.DeletedAt = null;
            return Task.CompletedTask;
        }

        public Task IncrementViewCountAsync(int id, CancellationToken cancellationToken = default) => Task.CompletedTask;
    }

    private sealed class FakeCategoryRepository : ICategoryRepository
    {
        private readonly List<Category> _categories =
        [
            new Category { Id = 1, Name = "사업자 세무", Slug = "business-tax", SortOrder = 1 }
        ];

        public Task<IEnumerable<Category>> GetAllAsync(CancellationToken cancellationToken = default) =>
            Task.FromResult<IEnumerable<Category>>(_categories);

        public Task<Category?> GetBySlugAsync(string slug, CancellationToken cancellationToken = default) =>
            Task.FromResult(_categories.FirstOrDefault(x => x.Slug == slug));

        public Task<Category?> GetByIdAsync(int id, CancellationToken cancellationToken = default) =>
            Task.FromResult(_categories.FirstOrDefault(x => x.Id == id));

        public Task<int> CreateAsync(Category category, CancellationToken cancellationToken = default)
        {
            category.Id = _categories.Count + 1;
            _categories.Add(category);
            return Task.FromResult(category.Id);
        }

        public Task UpdateAsync(Category category, CancellationToken cancellationToken = default) => Task.CompletedTask;

        public Task DeleteAsync(int id, CancellationToken cancellationToken = default) => Task.CompletedTask;
    }

    private sealed class PassThroughValidator<T> : IValidator<T>
    {
        public FluentValidation.Results.ValidationResult Validate(T instance) => new();
        public Task<FluentValidation.Results.ValidationResult> ValidateAsync(T instance, CancellationToken cancellation = default) => Task.FromResult(new FluentValidation.Results.ValidationResult());
        public FluentValidation.Results.ValidationResult Validate(IValidationContext context) => new();
        public Task<FluentValidation.Results.ValidationResult> ValidateAsync(IValidationContext context, CancellationToken cancellation = default) => Task.FromResult(new FluentValidation.Results.ValidationResult());
        public IValidatorDescriptor CreateDescriptor() => throw new NotImplementedException();
        public bool CanValidateInstancesOfType(Type type) => true;
    }
}
