using Microsoft.EntityFrameworkCore;
using Nexus.Api.Data;

namespace Nexus.Api.Repositories;

public sealed class UserRepository(NexusDbContext db) : IUserRepository
{
    public ValueTask<NexusUser?> GetByIdAsync(Guid userId, CancellationToken ct = default)
        => db.Users.FindAsync([userId], ct);

    public Task<NexusUser?> GetByEmailAsync(string normalizedEmail, CancellationToken ct = default)
        => db.Users.FirstOrDefaultAsync(u => u.NormalizedEmail == normalizedEmail, ct);

    public Task<List<NexusUser>> GetAllAsync(CancellationToken ct = default)
        => db.Users.OrderBy(u => u.CreatedAt).ToListAsync(ct);

    public Task<bool> AnyUsersAsync(CancellationToken ct = default)
        => db.Users.AnyAsync(ct);

    public async Task<NexusUser> AddAsync(NexusUser user, CancellationToken ct = default)
    {
        db.Users.Add(user);
        await db.SaveChangesAsync(ct);
        return user;
    }

    public Task UpdateAsync(NexusUser user, CancellationToken ct = default)
        => db.SaveChangesAsync(ct);

    public async Task DeleteAsync(NexusUser user, CancellationToken ct = default)
    {
        // Remove refresh tokens first
        var tokens = await db.RefreshTokens
            .Where(r => r.UserId == user.Id)
            .ToListAsync(ct);
        db.RefreshTokens.RemoveRange(tokens);
        db.Users.Remove(user);
        await db.SaveChangesAsync(ct);
    }

    public Task<RefreshToken?> GetRefreshTokenByHashAsync(string tokenHash, CancellationToken ct = default)
        => db.RefreshTokens
            .Include(r => r.User)
            .FirstOrDefaultAsync(r => r.TokenHash == tokenHash, ct);

    public Task<List<RefreshToken>> GetActiveTokensByFamilyAsync(Guid familyId, CancellationToken ct = default)
        => db.RefreshTokens
            .Where(r => r.FamilyId == familyId && r.RevokedAt == null)
            .ToListAsync(ct);

    public async Task AddRefreshTokenAsync(RefreshToken token, CancellationToken ct = default)
    {
        db.RefreshTokens.Add(token);
        await db.SaveChangesAsync(ct);
    }

    public Task UpdateRefreshTokenAsync(RefreshToken token, CancellationToken ct = default)
        => db.SaveChangesAsync(ct);

    public async Task RevokeTokenAsync(string tokenHash, CancellationToken ct = default)
    {
        var token = await db.RefreshTokens.FirstOrDefaultAsync(r => r.TokenHash == tokenHash, ct);
        if (token is null || token.RevokedAt is not null) return;

        token.RevokedAt = DateTimeOffset.UtcNow;
        token.ConcurrencyStamp = Guid.NewGuid();
        await db.SaveChangesAsync(ct);
    }

    public async Task RevokeFamilyAsync(Guid familyId, CancellationToken ct = default)
    {
        var activeTokens = await db.RefreshTokens
            .Where(r => r.FamilyId == familyId && r.RevokedAt == null)
            .ToListAsync(ct);

        if (activeTokens.Count == 0) return;

        var now = DateTimeOffset.UtcNow;
        foreach (var token in activeTokens)
        {
            token.RevokedAt = now;
            token.ConcurrencyStamp = Guid.NewGuid();
        }
        await db.SaveChangesAsync(ct);
    }

    public async Task RemoveExpiredTokensAsync(Guid userId, CancellationToken ct = default)
    {
        var cutoff = DateTimeOffset.UtcNow.AddDays(-30);
        var oldTokens = await db.RefreshTokens
            .Where(r => r.UserId == userId && (r.ExpiresAt < DateTimeOffset.UtcNow || r.RevokedAt < cutoff))
            .ToListAsync(ct);

        if (oldTokens.Count > 0)
        {
            db.RefreshTokens.RemoveRange(oldTokens);
            await db.SaveChangesAsync(ct);
        }
    }
}
