using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using timetracker.Shared;

namespace timetracker.Data;

public class AuthService(IDbContextFactory<TimetrackerDbContext> factory, UserNotificationService notifier) : IAuthService
{
    public async Task<User?> LoginAsync(string username, string password)
    {
        await using var db = await factory.CreateDbContextAsync();
        var user = await db.Users
            .FirstOrDefaultAsync(u => u.Username == username);
        if (user == null) return null;
        return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null;
    }

    public async Task<List<User>> GetAllUsersAsync()
    {
        await using var db = await factory.CreateDbContextAsync();
        return await db.Users
            .OrderBy(u => u.Username)
            .ToListAsync();
    }

    public async Task DeleteUserAsync(int userId)
    {
        await using var db = await factory.CreateDbContextAsync();
        var user = await db.Users.FindAsync(userId);
        if (user != null)
        {
            db.Users.Remove(user);
            await db.SaveChangesAsync();
            await notifier.NotifyUserDeletedAsync(userId);
            await notifier.NotifyUsersChangedAsync();
        }
    }

    public async Task<string?> RenameUserAsync(int userId, string newUsername)
    {
        if (string.IsNullOrWhiteSpace(newUsername) || newUsername.Length < 3)
            return "Benutzername muss mindestens 3 Zeichen lang sein.";

        await using var db = await factory.CreateDbContextAsync();
        if (await db.Users.AnyAsync(u => u.Username == newUsername && u.Id != userId))
            return "Benutzername bereits vergeben.";

        var user = await db.Users.FindAsync(userId);
        if (user == null) return "Benutzer nicht gefunden.";

        user.Username = newUsername;
        await db.SaveChangesAsync();
        await notifier.NotifyUsersChangedAsync();
        return null;
    }

    public async Task<(User? User, string? Error)> RegisterAsync(string username, string password, string? honeypot = null)    {
        if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
            return (null, "Benutzername muss mindestens 3 Zeichen lang sein.");
        if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
            return (null, "Passwort muss mindestens 6 Zeichen lang sein.");

        await using var db = await factory.CreateDbContextAsync();
        if (await db.Users.AnyAsync(u => u.Username == username))
            return (null, "Benutzername bereits vergeben.");

        var (hash, salt) = HashPassword(password);
        var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
        db.Users.Add(user);
        await db.SaveChangesAsync();
        await notifier.NotifyUsersChangedAsync();
        return (user, null);
    }

    private static (string hash, string salt) HashPassword(string password)
    {
        var saltBytes = RandomNumberGenerator.GetBytes(32);
        var salt = Convert.ToBase64String(saltBytes);
        var hash = ComputeHash(password, salt);
        return (hash, salt);
    }

    private static bool VerifyPassword(string password, string hash, string salt)
        => ComputeHash(password, salt) == hash;

    private static string ComputeHash(string password, string salt)
    {
        var hash = Rfc2898DeriveBytes.Pbkdf2(
            Encoding.UTF8.GetBytes(password),
            Convert.FromBase64String(salt),
            iterations: 200_000,
            HashAlgorithmName.SHA256,
            outputLength: 32);
        return Convert.ToBase64String(hash);
    }
}
