using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace ErsatzTV.Application.Auth;

public class SeedLocalAdminFromEnvironmentHandler(
    IDbContextFactory<TvContext> dbContextFactory,
    ILocalPasswordHasher passwordHasher)
    : IRequestHandler<SeedLocalAdminFromEnvironment, Either<BaseError, Unit>>
{
    public async Task<Either<BaseError, Unit>> Handle(
        SeedLocalAdminFromEnvironment request,
        CancellationToken cancellationToken)
    {
        string username = (request.Username ?? string.Empty).Trim();
        if (username.Length == 0)
        {
            username = "admin";
        }

        if (username.Length > LocalAdminHelpers.MaxUsernameLength)
        {
            return BaseError.New("Seed username is too long");
        }

        foreach (BaseError error in LocalAdminHelpers.ValidatePassword(request.Password))
        {
            return error;
        }

        await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);

        List<ConfigElement> rows = await dbContext.ConfigElements
            .Where(c => c.Key == ConfigElementKey.AuthLocalAdminUsername.Key
                        || c.Key == ConfigElementKey.AuthLocalAdminPasswordHash.Key
                        || c.Key == ConfigElementKey.AuthSecurityStamp.Key)
            .ToListAsync(cancellationToken);

        // Overwrite (recovery/bootstrap) atomically: username + new hash + rotated stamp commit together.
        Upsert(dbContext, rows, ConfigElementKey.AuthLocalAdminUsername.Key, username);
        Upsert(dbContext, rows, ConfigElementKey.AuthLocalAdminPasswordHash.Key, passwordHasher.Hash(request.Password));
        Upsert(dbContext, rows, ConfigElementKey.AuthSecurityStamp.Key, LocalAdminHelpers.NewSecurityStamp());

        await dbContext.SaveChangesAsync(cancellationToken);

        return Unit.Default;
    }

    private static void Upsert(TvContext dbContext, List<ConfigElement> existing, string key, string value)
    {
        ConfigElement row = existing.Find(r => r.Key == key);
        if (row is null)
        {
            dbContext.ConfigElements.Add(new ConfigElement { Key = key, Value = value });
        }
        else
        {
            row.Value = value;
        }
    }
}
