using Microsoft.EntityFrameworkCore;
using UniVerse.Application.DTOs.Tags;
using UniVerse.Application.Interfaces;
using UniVerse.Application.Mappings;
using UniVerse.Domain.Entities;
using UniVerse.Domain.Enums;
using UniVerse.Domain.Exceptions;
using UniVerse.Infrastructure.Data;

namespace UniVerse.Infrastructure.Services;

public class TagService : ITagService
{
    private readonly AppDbContext _db;

    public TagService(AppDbContext db) => _db = db;

    public async Task<List<TagDto>> GetAllAsync(TagType? type = null, int? parentId = null)
    {
        var query = _db.Tags.AsQueryable();
        if (type.HasValue) query = query.Where(t => t.Type == type.Value);
        if (parentId.HasValue) query = query.Where(t => t.ParentId == parentId.Value);

        return await query.OrderBy(t => t.Name)
            .Select(t => new TagDto(t.Id, t.Name, t.Type, t.ParentId, t.CreatedAt))
            .ToListAsync();
    }

    public async Task<TagDto> GetByIdAsync(int id)
    {
        var tag = await _db.Tags.FindAsync(id)
            ?? throw new NotFoundException("Tag", id);
        return tag.ToDto();
    }

    public async Task<TagDto> CreateAsync(CreateTagRequest request)
    {
        if (request.ParentId.HasValue)
        {
            var parent = await _db.Tags.FindAsync(request.ParentId.Value)
                ?? throw new NotFoundException("Parent Tag", request.ParentId.Value);
        }

        var tag = new Tag
        {
            Name = request.Name,
            Type = request.Type,
            ParentId = request.ParentId
        };

        _db.Tags.Add(tag);
        await _db.SaveChangesAsync();
        return tag.ToDto();
    }

    public async Task<TagDto> UpdateAsync(int id, UpdateTagRequest request)
    {
        var tag = await _db.Tags.FindAsync(id)
            ?? throw new NotFoundException("Tag", id);

        tag.Name = request.Name;
        tag.Type = request.Type;
        tag.ParentId = request.ParentId;

        await _db.SaveChangesAsync();
        return tag.ToDto();
    }

    public async Task DeleteAsync(int id)
    {
        var tag = await _db.Tags.FindAsync(id)
            ?? throw new NotFoundException("Tag", id);
        _db.Tags.Remove(tag);
        await _db.SaveChangesAsync();
    }

    public async Task<List<TagTreeDto>> GetTreeAsync()
    {
        var allTags = await _db.Tags
            .Include(t => t.Children)
            .ToListAsync();

        var roots = allTags.Where(t => t.ParentId == null).ToList();
        return roots.Select(t => BuildTree(t, allTags)).ToList();
    }

    private TagTreeDto BuildTree(Tag tag, List<Tag> allTags)
    {
        var children = allTags.Where(t => t.ParentId == tag.Id).ToList();
        return new TagTreeDto(
            tag.Id, tag.Name, tag.Type,
            children.Select(c => BuildTree(c, allTags)).ToList()
        );
    }
}
