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

namespace ERPCore.Services.Hrm;

/// <inheritdoc cref="IEmployeeUserLinkService"/>
public sealed class EmployeeUserLinkService : IEmployeeUserLinkService
{
    private readonly IRepository<Employee> _employees;
    private readonly IRepository<User> _users;
    private readonly IUnitOfWork _uow;

    public EmployeeUserLinkService(IRepository<Employee> employees, IRepository<User> users, IUnitOfWork uow)
    {
        _employees = employees;
        _users = users;
        _uow = uow;
    }

    public async Task<EmployeeMatchDto?> FindStaffCandidateByEmailAsync(string email, CancellationToken ct = default)
    {
        var term = email.Trim();
        var employee = await _employees.Query().AsNoTracking()
            .Where(e => e.UserId == null && e.Email != null && EF.Functions.ILike(e.Email, term))
            .Select(e => new EmployeeMatchDto(e.EmployeeId, e.EmployeeCode, e.FullName, e.Email!))
            .FirstOrDefaultAsync(ct);
        return employee;
    }

    public async Task<UserMatchDto?> FindUserCandidateByEmailAsync(string email, CancellationToken ct = default)
    {
        var term = email.Trim();
        var linkedUserIds = _employees.Query().Where(e => e.UserId != null).Select(e => e.UserId!.Value);

        var user = await _users.Query().AsNoTracking()
            .Where(u => u.Email != null && EF.Functions.ILike(u.Email, term) && !linkedUserIds.Contains(u.UserId))
            .Select(u => new UserMatchDto(u.UserId, u.Username, u.DisplayName, u.Email!))
            .FirstOrDefaultAsync(ct);
        return user;
    }

    public async Task LinkAsync(int employeeId, int userId, CancellationToken ct = default)
    {
        var employee = await _employees.GetByIdAsync(employeeId, ct)
            ?? throw new NotFoundException($"Employee {employeeId} was not found.");
        if (employee.UserId is not null)
            throw new DomainException(ErrorCodes.EmployeeAlreadyLinked, $"Employee {employeeId} already has a linked user.", 409);

        var user = await _users.GetByIdAsync(userId, ct)
            ?? throw new NotFoundException($"User {userId} was not found.");
        if (await _employees.Query().AnyAsync(e => e.UserId == userId, ct))
            throw new DomainException(ErrorCodes.UserAlreadyLinked, $"User {userId} already backs a different employee.", 409);

        employee.UserId = user.UserId;
        employee.UpdatedAt = DateTime.UtcNow;
        await _uow.SaveChangesAsync(ct);
    }

    public async Task UnlinkAsync(int employeeId, CancellationToken ct = default)
    {
        var employee = await _employees.GetByIdAsync(employeeId, ct)
            ?? throw new NotFoundException($"Employee {employeeId} was not found.");

        employee.UserId = null;
        employee.UpdatedAt = DateTime.UtcNow;
        await _uow.SaveChangesAsync(ct);
    }
}
