using System.Text.RegularExpressions;
using ASTRAIN.Api.Data;
using ASTRAIN.Shared.Models;
using Microsoft.EntityFrameworkCore;

namespace ASTRAIN.Api.Services;

/// <summary>
/// Provides helper methods for ensuring and validating application users.
/// </summary>
internal static class UserProvisioning
{
    /// <summary>
    /// Ensures a user exists in the database and returns the user record.
    /// </summary>
    /// <param name="db">The application database context.</param>
    /// <param name="userId">Optional user id to validate or create.</param>
    /// <param name="populateSampleData">Whether to populate sample data for new users.</param>
    /// <returns>The existing or newly created user.</returns>
    public static async Task<User> EnsureUserAsync(AppDbContext db, string? userId, bool populateSampleData)
    {
        if (!string.IsNullOrWhiteSpace(userId) && IsValidUserId(userId))
        {
            var existing = await db.Users.FirstOrDefaultAsync(u => u.Id == userId);
            if (existing is not null)
            {
                return existing;
            }

            var created = new User { Id = userId };
            db.Users.Add(created);
            await db.SaveChangesAsync();
            if (populateSampleData)
            {
                await PopulateSampleDataAsync(db, created);
            }
            return created;
        }

        while (true)
        {
            var newId = UserKeyGenerator.Generate(8);
            var exists = await db.Users.AnyAsync(u => u.Id == newId);
            if (exists)
            {
                continue;
            }

            var user = new User { Id = newId };
            db.Users.Add(user);
            await db.SaveChangesAsync();
            if (populateSampleData)
            {
                await PopulateSampleDataAsync(db, user);
            }
            return user;
        }
    }

    /// <summary>
    /// Populates a new user with sample exercises and routines for debugging.
    /// </summary>
    /// <param name="db">The application database context.</param>
    /// <param name="user">The newly created user.</param>
    private static async Task PopulateSampleDataAsync(AppDbContext db, User user)
    {
        // Sample exercises
        var exercises = new[]
        {
            new Exercise { Name = "Push-ups", UserId = user.Id },
            new Exercise { Name = "Squats", UserId = user.Id },
            new Exercise { Name = "Pull-ups", UserId = user.Id },
            new Exercise { Name = "Bench Press", UserId = user.Id },
            new Exercise { Name = "Deadlift", UserId = user.Id }
        };

        db.Exercises.AddRange(exercises);
        await db.SaveChangesAsync();

        // Sample routines
        var routine1 = new Routine { Name = "Upper Body", UserId = user.Id };
        var routine2 = new Routine { Name = "Lower Body", UserId = user.Id };

        db.Routines.AddRange(routine1, routine2);
        await db.SaveChangesAsync();

        // Associate exercises with routines
        var pushUps = exercises.First(e => e.Name == "Push-ups");
        var pullUps = exercises.First(e => e.Name == "Pull-ups");
        var benchPress = exercises.First(e => e.Name == "Bench Press");
        var squats = exercises.First(e => e.Name == "Squats");
        var deadlift = exercises.First(e => e.Name == "Deadlift");

        var routineExercises1 = new[]
        {
            new RoutineExercise { RoutineId = routine1.Id, ExerciseId = pushUps.Id, Order = 0 },
            new RoutineExercise { RoutineId = routine1.Id, ExerciseId = pullUps.Id, Order = 1 },
            new RoutineExercise { RoutineId = routine1.Id, ExerciseId = benchPress.Id, Order = 2 }
        };

        var routineExercises2 = new[]
        {
            new RoutineExercise { RoutineId = routine2.Id, ExerciseId = squats.Id, Order = 0 },
            new RoutineExercise { RoutineId = routine2.Id, ExerciseId = deadlift.Id, Order = 1 }
        };

        db.RoutineExercises.AddRange(routineExercises1);
        db.RoutineExercises.AddRange(routineExercises2);
        await db.SaveChangesAsync();
    }

    /// <summary>
    /// Determines whether a user id matches the expected format.
    /// </summary>
    /// <param name="userId">The user id to validate.</param>
    /// <returns><c>true</c> if the id is valid; otherwise <c>false</c>.</returns>
    public static bool IsValidUserId(string userId)
    {
        return Regex.IsMatch(userId, "^[A-Za-z0-9]{8}$");
    }
}
