using ERPCore.Domain.Entities;
using ERPCore.Dtos.Categories;
using ERPCore.Dtos.Common;
using ERPCore.Infra.UoW;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using ERPCore.System.Errors;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services;

public sealed class CategoryService : ICategoryService
{
    private readonly IRepository<Category> _categories;
    private readonly IUnitOfWork _uow;

    public CategoryService(IRepository<Category> categories, IUnitOfWork uow)
    {
        _categories = categories;
        _uow = uow;
    }

    public async Task<PagedResponse<CategoryDto>> ListAsync(PageQuery query, CancellationToken ct = default)
    {
        var q = _categories.Query().AsNoTracking();
        if (!string.IsNullOrWhiteSpace(query.Q))
        {
            var term = query.Q.Trim();
            q = q.Where(c => EF.Functions.ILike(c.Name, $"%{term}%"));
        }

        var total = await q.CountAsync(ct);
        var rows = await q.OrderBy(c => c.Name)
            .Skip(query.Skip).Take(query.PageSize)
            .Select(c => new CategoryDto(c.CategoryId, c.Name, c.ParentId))
            .ToListAsync(ct);

        return PagedResponse<CategoryDto>.Create(rows, query.Page, query.PageSize, total);
    }

    public async Task<IReadOnlyList<CategoryTreeDto>> GetTreeAsync(CancellationToken ct = default)
    {
        var all = await _categories.Query().AsNoTracking()
            .OrderBy(c => c.Name)
            .Select(c => new CategoryDto(c.CategoryId, c.Name, c.ParentId))
            .ToListAsync(ct);

        var byParent = all.ToLookup(c => c.ParentId);

        List<CategoryTreeDto> Build(int? parentId) =>
            byParent[parentId]
                .Select(c => new CategoryTreeDto(c.CategoryId, c.Name, c.ParentId, Build(c.CategoryId)))
                .ToList();

        return Build(null);
    }

    public async Task<CategoryDto> CreateAsync(CreateCategoryRequest request, CancellationToken ct = default)
    {
        if (request.ParentId is not null
            && !await _categories.Query().AnyAsync(c => c.CategoryId == request.ParentId, ct))
            throw new DomainException(ErrorCodes.Validation, $"Parent category {request.ParentId} does not exist.", 422);

        var category = new Category { Name = request.Name.Trim(), ParentId = request.ParentId };
        await _categories.AddAsync(category, ct);
        await _uow.SaveChangesAsync(ct);

        return new CategoryDto(category.CategoryId, category.Name, category.ParentId);
    }
}
