using FluentValidation;
using MediatR;
using Microsoft.EntityFrameworkCore;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.Common.Models;
using SolutionErp.Domain.Master;

namespace SolutionErp.Application.Master.Departments;

public record DepartmentDto(
    Guid Id,
    string Code,
    string Name,
    Guid? ManagerUserId,
    string? Note,
    Guid? ParentId,
    DateTime CreatedAt,
    DateTime? UpdatedAt);

// ===== Cây tổ chức (Department hierarchy) — nền trang Hồ sơ Nhân sự =====
// DirectEmployeeCount = NV active thuộc trực tiếp phòng. TotalEmployeeCount =
// Direct + Σ con đệ quy (rollup). Children sort theo Code ổn định.
public record DepartmentTreeNodeDto(
    Guid Id,
    string Code,
    string Name,
    Guid? ParentId,
    int DirectEmployeeCount,
    int TotalEmployeeCount,
    List<DepartmentTreeNodeDto> Children);

public record GetDepartmentTreeQuery : IRequest<List<DepartmentTreeNodeDto>>;

public class GetDepartmentTreeQueryHandler(IApplicationDbContext db) : IRequestHandler<GetDepartmentTreeQuery, List<DepartmentTreeNodeDto>>
{
    public async Task<List<DepartmentTreeNodeDto>> Handle(GetDepartmentTreeQuery request, CancellationToken ct)
    {
        // 1) Load tất cả phòng phẳng (HasQueryFilter !IsDeleted tự áp).
        var flat = await db.Departments.AsNoTracking()
            .Select(d => new { d.Id, d.Code, d.Name, d.ParentId })
            .ToListAsync(ct);

        // 2) Đếm NV active per-dept. Nguồn org-chart = User.DepartmentId
        //    (EmployeeProfile KHÔNG có DepartmentId — liên kết qua UserId; field
        //    phòng ban nằm trên User entity từ Mig 11). Active = IsActive=true.
        var directCounts = await db.Users.AsNoTracking()
            .Where(u => u.DepartmentId != null && u.IsActive)
            .GroupBy(u => u.DepartmentId!.Value)
            .Select(g => new { DeptId = g.Key, Count = g.Count() })
            .ToDictionaryAsync(x => x.DeptId, x => x.Count, ct);

        // 3) Dựng node + index theo ParentId.
        var nodes = flat.ToDictionary(
            f => f.Id,
            f => new DepartmentTreeNodeDto(
                f.Id, f.Code, f.Name, f.ParentId,
                directCounts.TryGetValue(f.Id, out var c) ? c : 0,
                0, // TotalEmployeeCount tính sau khi ráp tree
                new List<DepartmentTreeNodeDto>()));

        var roots = new List<DepartmentTreeNodeDto>();
        foreach (var f in flat)
        {
            var node = nodes[f.Id];
            // Root = ParentId null HOẶC trỏ tới phòng không tồn tại (orphan an toàn coi như root).
            if (f.ParentId is Guid pid && nodes.TryGetValue(pid, out var parent))
                parent.Children.Add(node);
            else
                roots.Add(node);
        }

        // 4) Rollup Total + sort Children theo Code — đệ quy có cycle-guard
        //    (HashSet visited Id) để chống vòng lặp vô hạn nếu data cha-trỏ-vòng.
        var visited = new HashSet<Guid>();
        var result = new List<DepartmentTreeNodeDto>();
        foreach (var root in roots.OrderBy(r => r.Code, StringComparer.Ordinal))
        {
            var rolled = RollupAndSort(root, visited);
            if (rolled is not null) result.Add(rolled);
        }
        return result;
    }

    // Trả node mới với TotalEmployeeCount = Direct + Σ(Children.Total) + Children
    // đã sort & rollup. Cycle-guard: node đã thăm → bỏ qua (return null).
    private static DepartmentTreeNodeDto? RollupAndSort(DepartmentTreeNodeDto node, HashSet<Guid> visited)
    {
        if (!visited.Add(node.Id)) return null; // đã thăm → cắt vòng lặp

        var children = new List<DepartmentTreeNodeDto>();
        foreach (var child in node.Children.OrderBy(c => c.Code, StringComparer.Ordinal))
        {
            var rolled = RollupAndSort(child, visited);
            if (rolled is not null) children.Add(rolled);
        }

        var total = node.DirectEmployeeCount + children.Sum(c => c.TotalEmployeeCount);
        return node with { Children = children, TotalEmployeeCount = total };
    }
}

public record ListDepartmentsQuery : PagedRequest, IRequest<PagedResult<DepartmentDto>>;

public class ListDepartmentsQueryHandler(IApplicationDbContext db) : IRequestHandler<ListDepartmentsQuery, PagedResult<DepartmentDto>>
{
    public async Task<PagedResult<DepartmentDto>> Handle(ListDepartmentsQuery request, CancellationToken ct)
    {
        var query = db.Departments.AsNoTracking();
        if (!string.IsNullOrWhiteSpace(request.Search))
        {
            var s = request.Search.Trim();
            query = query.Where(x => x.Code.Contains(s) || x.Name.Contains(s));
        }
        query = (request.SortBy, request.SortDesc) switch
        {
            ("code", true) => query.OrderByDescending(x => x.Code),
            ("code", false) => query.OrderBy(x => x.Code),
            ("name", true) => query.OrderByDescending(x => x.Name),
            ("name", false) => query.OrderBy(x => x.Name),
            _ => query.OrderByDescending(x => x.CreatedAt),
        };
        var total = await query.CountAsync(ct);
        var items = await query
            .Skip((request.Page - 1) * request.PageSize).Take(request.PageSize)
            .Select(x => new DepartmentDto(x.Id, x.Code, x.Name, x.ManagerUserId, x.Note, x.ParentId, x.CreatedAt, x.UpdatedAt))
            .ToListAsync(ct);
        return new PagedResult<DepartmentDto>(items, total, request.Page, request.PageSize);
    }
}

public record GetDepartmentQuery(Guid Id) : IRequest<DepartmentDto>;

public class GetDepartmentQueryHandler(IApplicationDbContext db) : IRequestHandler<GetDepartmentQuery, DepartmentDto>
{
    public async Task<DepartmentDto> Handle(GetDepartmentQuery request, CancellationToken ct)
    {
        var x = await db.Departments.AsNoTracking().FirstOrDefaultAsync(p => p.Id == request.Id, ct)
            ?? throw new NotFoundException("Department", request.Id);
        return new DepartmentDto(x.Id, x.Code, x.Name, x.ManagerUserId, x.Note, x.ParentId, x.CreatedAt, x.UpdatedAt);
    }
}

public record CreateDepartmentCommand(string Code, string Name, Guid? ManagerUserId, string? Note, Guid? ParentId) : IRequest<Guid>;

public class CreateDepartmentCommandValidator : AbstractValidator<CreateDepartmentCommand>
{
    public CreateDepartmentCommandValidator()
    {
        RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
        RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
    }
}

public class CreateDepartmentCommandHandler(IApplicationDbContext db) : IRequestHandler<CreateDepartmentCommand, Guid>
{
    public async Task<Guid> Handle(CreateDepartmentCommand request, CancellationToken ct)
    {
        if (await db.Departments.AnyAsync(x => x.Code == request.Code, ct))
            throw new ConflictException($"Mã phòng ban '{request.Code}' đã tồn tại.");
        var entity = new Department
        {
            Code = request.Code, Name = request.Name,
            ManagerUserId = request.ManagerUserId, Note = request.Note,
            ParentId = request.ParentId,
        };
        db.Departments.Add(entity);
        await db.SaveChangesAsync(ct);
        return entity.Id;
    }
}

public record UpdateDepartmentCommand(Guid Id, string Code, string Name, Guid? ManagerUserId, string? Note, Guid? ParentId) : IRequest;

public class UpdateDepartmentCommandValidator : AbstractValidator<UpdateDepartmentCommand>
{
    public UpdateDepartmentCommandValidator()
    {
        RuleFor(x => x.Id).NotEmpty();
        RuleFor(x => x.Code).NotEmpty().MaximumLength(50);
        RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
    }
}

public class UpdateDepartmentCommandHandler(IApplicationDbContext db) : IRequestHandler<UpdateDepartmentCommand>
{
    public async Task Handle(UpdateDepartmentCommand request, CancellationToken ct)
    {
        var entity = await db.Departments.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("Department", request.Id);
        if (entity.Code != request.Code && await db.Departments.AnyAsync(x => x.Code == request.Code && x.Id != request.Id, ct))
            throw new ConflictException($"Mã phòng ban '{request.Code}' đã tồn tại.");

        // Chống vòng lặp phân cấp khi gán phòng cha: (1) không tự làm cha mình;
        // (2) không gán cha là phòng con-cháu của chính nó — đi ngược chuỗi cha
        // từ phòng cha mới, nếu gặp lại Id này ⇒ sẽ tạo vòng. HashSet guard cắt
        // loop nếu data cũ đã lỗi (cha-trỏ-vòng sẵn).
        if (request.ParentId is Guid newParent)
        {
            if (newParent == request.Id)
                throw new ConflictException("Phòng không thể là cấp cha của chính nó.");
            var cursor = newParent;
            var guard = new HashSet<Guid>();
            while (guard.Add(cursor))
            {
                var grandParent = await db.Departments.AsNoTracking()
                    .Where(x => x.Id == cursor).Select(x => x.ParentId).FirstOrDefaultAsync(ct);
                if (grandParent is null) break;
                if (grandParent.Value == request.Id)
                    throw new ConflictException("Không thể gán: sẽ tạo vòng lặp phân cấp phòng ban.");
                cursor = grandParent.Value;
            }
        }

        entity.Code = request.Code;
        entity.Name = request.Name;
        entity.ManagerUserId = request.ManagerUserId;
        entity.Note = request.Note;
        entity.ParentId = request.ParentId;
        await db.SaveChangesAsync(ct);
    }
}

public record DeleteDepartmentCommand(Guid Id) : IRequest;

public class DeleteDepartmentCommandHandler(IApplicationDbContext db) : IRequestHandler<DeleteDepartmentCommand>
{
    public async Task Handle(DeleteDepartmentCommand request, CancellationToken ct)
    {
        var entity = await db.Departments.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
            ?? throw new NotFoundException("Department", request.Id);
        db.Departments.Remove(entity);
        await db.SaveChangesAsync(ct);
    }
}
