using Microsoft.EntityFrameworkCore;
using RpgRoller.Data;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public sealed class GamePersistenceService(IDbContextFactory<RpgRollerDbContext> dbContextFactory, GameStateStore stateStore)
{
    public void LoadStateFromDatabase()
    {
        using var db = dbContextFactory.CreateDbContext();
        var users = db.Users.AsNoTracking().ToList();
        var sessions = db.Sessions.AsNoTracking().ToList();
        var campaigns = db.Campaigns.AsNoTracking().ToList();
        var characters = db.Characters.AsNoTracking().ToList();
        var skillGroups = db.SkillGroups.AsNoTracking().ToList();
        var skills = db.Skills.AsNoTracking().ToList();
        var logEntries = db.RollLogEntries.AsNoTracking().ToList().OrderBy(x => x.TimestampUtc).ThenBy(x => x.Id).ToList();

        lock (stateStore.Gate)
        {
            stateStore.UsersById.Clear();
            stateStore.UserIdsByUsername.Clear();
            stateStore.SessionsByToken.Clear();
            stateStore.CampaignsById.Clear();
            stateStore.CharactersById.Clear();
            stateStore.SkillGroupsById.Clear();
            stateStore.SkillsById.Clear();
            stateStore.RollLog.Clear();

            foreach (var user in users)
            {
                var normalizedUsername = string.IsNullOrWhiteSpace(user.UsernameNormalized) ? NormalizeUsername(user.Username) : user.UsernameNormalized;

                var storedUser = new UserAccount
                {
                    Id = user.Id,
                    Username = user.Username,
                    UsernameNormalized = normalizedUsername,
                    PasswordHash = user.PasswordHash,
                    DisplayName = user.DisplayName,
                    Roles = string.IsNullOrWhiteSpace(user.Roles) ? string.Empty : RoleSerializer.Serialize(RoleSerializer.Parse(user.Roles)),
                    ActiveCharacterId = user.ActiveCharacterId
                };
                stateStore.UsersById[storedUser.Id] = storedUser;
                stateStore.UserIdsByUsername[normalizedUsername] = storedUser.Id;
            }

            foreach (var session in sessions)
            {
                if (stateStore.UsersById.ContainsKey(session.UserId))
                    stateStore.SessionsByToken[session.Token] = GameStateCloneFactory.CloneSession(session);
            }

            foreach (var campaign in campaigns)
                stateStore.CampaignsById[campaign.Id] = GameStateCloneFactory.CloneCampaign(campaign);

            foreach (var character in characters)
                stateStore.CharactersById[character.Id] = GameStateCloneFactory.CloneCharacter(character);

            foreach (var skillGroup in skillGroups)
                stateStore.SkillGroupsById[skillGroup.Id] = GameStateCloneFactory.CloneSkillGroup(skillGroup);

            foreach (var skill in skills)
                stateStore.SkillsById[skill.Id] = GameStateCloneFactory.CloneSkill(skill);

            stateStore.RollLog.AddRange(logEntries.Select(GameStateCloneFactory.CloneRollLogEntry));
        }
    }

    public void PersistStateLocked()
    {
        using var db = dbContextFactory.CreateDbContext();
        using var transaction = db.Database.BeginTransaction();

        db.RollLogEntries.ExecuteDelete();
        db.Skills.ExecuteDelete();
        db.SkillGroups.ExecuteDelete();
        db.Characters.ExecuteDelete();
        db.Campaigns.ExecuteDelete();
        db.Sessions.ExecuteDelete();
        db.Users.ExecuteDelete();

        lock (stateStore.Gate)
        {
            db.Users.AddRange(stateStore.UsersById.Values.Select(GameStateCloneFactory.CloneUser));
            db.Sessions.AddRange(stateStore.SessionsByToken.Values.Select(GameStateCloneFactory.CloneSession));
            db.Campaigns.AddRange(stateStore.CampaignsById.Values.Select(GameStateCloneFactory.CloneCampaign));
            db.Characters.AddRange(stateStore.CharactersById.Values.Select(GameStateCloneFactory.CloneCharacter));
            db.SkillGroups.AddRange(stateStore.SkillGroupsById.Values.Select(GameStateCloneFactory.CloneSkillGroup));
            db.Skills.AddRange(stateStore.SkillsById.Values.Select(GameStateCloneFactory.CloneSkill));
            db.RollLogEntries.AddRange(stateStore.RollLog.Select(GameStateCloneFactory.CloneRollLogEntry));
        }

        db.SaveChanges();
        transaction.Commit();
    }

    private static string NormalizeUsername(string username)
    {
        return username.ToUpperInvariant();
    }
}