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

namespace ErsatzTV.Application.Auth;

public class ClaimLocalAdminHandler(IDbContextFactory<TvContext> dbContextFactory, ILocalPasswordHasher passwordHasher)
    : IRequestHandler<ClaimLocalAdmin, Either<BaseError, LocalAdminPrincipal>>
{
    public async Task<Either<BaseError, LocalAdminPrincipal>> Handle(
        ClaimLocalAdmin request,
        CancellationToken cancellationToken)
    {
        foreach (BaseError error in LocalAdminHelpers.ValidateNewCredentials(request.Username, request.Password))
        {
            return error;
        }

        string username = request.Username.Trim();

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

        // Fast path for the common already-configured case (clean 409). The real first-claim-wins guard is
        // the unique index on ConfigElement.Key + the single atomic SaveChanges below: two concurrent claims
        // both pass this check, but only one INSERT of the three credential rows commits — the loser's
        // SaveChanges violates the unique Key index and rolls back wholesale (no mixed-state credential).
        bool alreadyConfigured = await dbContext.ConfigElements
            .AnyAsync(c => c.Key == ConfigElementKey.AuthLocalAdminPasswordHash.Key, cancellationToken);
        if (alreadyConfigured)
        {
            return BaseError.New("A local administrator has already been configured");
        }

        string stamp = LocalAdminHelpers.NewSecurityStamp();
        dbContext.ConfigElements.AddRange(
            new ConfigElement { Key = ConfigElementKey.AuthLocalAdminUsername.Key, Value = username },
            new ConfigElement
            {
                Key = ConfigElementKey.AuthLocalAdminPasswordHash.Key,
                Value = passwordHasher.Hash(request.Password)
            },
            new ConfigElement { Key = ConfigElementKey.AuthSecurityStamp.Key, Value = stamp });

        try
        {
            await dbContext.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateException)
        {
            // A write conflict here is (almost always) a lost first-claim race — a concurrent claim inserted
            // these keys first (unique Key index). Confirm the row now exists on a fresh context before
            // reporting "already configured"; otherwise this was a genuine/transient DB error → rethrow rather
            // than mask it.
            await using TvContext verifyContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
            bool nowConfigured = await verifyContext.ConfigElements
                .AnyAsync(c => c.Key == ConfigElementKey.AuthLocalAdminPasswordHash.Key, cancellationToken);
            if (nowConfigured)
            {
                return BaseError.New("A local administrator has already been configured");
            }

            throw;
        }

        return new LocalAdminPrincipal(username, stamp);
    }
}
