namespace CSR.Infrastructure.Persistence.Repositories;

using CSR.Domain.Entities;
using CSR.Application.Interfaces;
using Microsoft.EntityFrameworkCore;

public class UserRepository(CSRDbContext context) : IUserRepository
{
  private readonly CSRDbContext _context = context;

  public async Task<User?> GetByIdAsync(int id)
  {
    var userEntity = await _context.Users
        .Include(u => u.Role)
        .SingleOrDefaultAsync(u => u.Id == id);

    if (userEntity == null)
    {
      return null; // No entity found, return null domain model
    }

    var user = User.LoadExisting(
        userEntity.Id,
        userEntity.Username,
        userEntity.Email,
        userEntity.PasswordHash,
        userEntity.RoleId,
        Role.LoadExisting(
          userEntity.Role.Id,
          userEntity.Role.Name
          )
    );

    return user;
  }


  public async Task<User?> GetByUsernameAsync(string username)
  {
    var userEntity = await _context.Users
        .Include(u => u.Role)
        .SingleOrDefaultAsync(u => u.Username == username);

    if (userEntity == null)
    {
      return null; // No entity found, return null domain model
    }

    var user = User.LoadExisting(
        userEntity.Id,
        userEntity.Username,
        userEntity.Email,
        userEntity.PasswordHash,
        userEntity.RoleId,
        Role.LoadExisting(
          userEntity.Role.Id,
          userEntity.Role.Name
          )
    );

    return user;
  }


  public async Task<IEnumerable<User>?> GetAllByRoleIdAsync(int roleId)
  {
    var roleEntity = await _context.Roles
        .Include(r => r.Users)
        .FirstOrDefaultAsync(r => r.Id == roleId);

    if (roleEntity == null)
    {
      return null; // No entity found, return null
    }

    var users = roleEntity.Users
        .Select(userEntity => User.LoadExisting(
            userEntity.Id,
            userEntity.Username,
            userEntity.Email,
            userEntity.PasswordHash,
            userEntity.RoleId,
            Role.LoadExisting(
              userEntity.Role.Id,
              userEntity.Role.Name
              )
        ));

    return users;
  }


  public async Task<User?> AddAsync(User user)
  {
    var roleEntity = await _context.Roles
        .SingleOrDefaultAsync(r => r.Id == user.RoleId)
        ?? throw new InvalidOperationException($"Role with ID {user.RoleId} does not exist.");

    var userEntity = new Persistence.User
    {
      Username = user.Username,
      Email = user.Email,
      PasswordHash = user.PasswordHash,
      RoleId = user.RoleId,
      Role = roleEntity
    };

    try
    {
      _context.Users.Add(userEntity);
    }
    catch (DbUpdateException)
    {
      return null;
    }

    await _context.SaveChangesAsync();
    return User.LoadExisting(
        userEntity.Id,
        userEntity.Username,
        userEntity.Email,
        userEntity.PasswordHash,
        userEntity.RoleId,
        Role.LoadExisting(
          userEntity.Role.Id,
          userEntity.Role.Name
          )
    );
  }


  public async Task UpdateAsync(User user)
  {
    var userEntity = await _context.Users
        .Include(u => u.Role)
        .FirstOrDefaultAsync(u => u.Id == user.Id);

    if (userEntity == null)
    {
      // NOTE should I throw an exception here?
      return;
    }


    userEntity.Id = user.Id;
    userEntity.Username = user.Username;
    userEntity.Email = user.Email;
    userEntity.PasswordHash = user.PasswordHash;
    userEntity.RoleId = user.RoleId;
    userEntity.Role = new Persistence.Role { Id = user.Role.Id, Name = user.Role.Name };

    // Prevent EF from trying to update the Role entity
    _context.Entry(userEntity.Role).State = EntityState.Unchanged;

    _context.Users.Update(userEntity);
    await _context.SaveChangesAsync();
  }


  public async Task DeleteAsync(int id)
  {
    var userEntity = new Persistence.User
    {
      Id = id,
      Username = string.Empty,
      Email = string.Empty,
      PasswordHash = string.Empty,
      RoleId = 0
    };

    _context.Users.Remove(userEntity);
    await _context.SaveChangesAsync();
  }
}
