using LctMonolith.Models.Database;
using LctMonolith.Services.Models;
using Microsoft.EntityFrameworkCore;
using Serilog;
using System.Text.Json;
using System.Linq;
using LctMonolith.Database.UnitOfWork;
using LctMonolith.Services.Contracts;
namespace LctMonolith.Services;
///
/// Mission management and user mission state transitions.
///
public class MissionService : IMissionService
{
private readonly IUnitOfWork _uow;
private readonly IGamificationService _gamification;
public MissionService(IUnitOfWork uow, IGamificationService gamification)
{
_uow = uow;
_gamification = gamification;
}
public async Task CreateMissionAsync(CreateMissionModel model, CancellationToken ct = default)
{
var mission = new Mission
{
Title = model.Title,
Description = model.Description,
Branch = model.Branch,
Category = model.Category,
MinRankId = model.MinRankId,
ExperienceReward = model.ExperienceReward,
ManaReward = model.ManaReward,
IsActive = true
};
await _uow.Missions.AddAsync(mission, ct);
foreach (var cr in model.CompetencyRewards)
{
await _uow.MissionCompetencyRewards.AddAsync(new MissionCompetencyReward
{
MissionId = mission.Id,
CompetencyId = cr.CompetencyId,
LevelDelta = cr.LevelDelta,
ProgressPointsDelta = cr.ProgressPointsDelta
}, ct);
}
foreach (var artId in model.ArtifactRewardIds.Distinct())
{
await _uow.MissionArtifactRewards.AddAsync(new MissionArtifactReward
{
MissionId = mission.Id,
ArtifactId = artId
}, ct);
}
await _uow.SaveChangesAsync(ct);
await LogEventAsync(EventType.MissionStatusChanged, null, new { action = "created", missionId = mission.Id }, ct);
return mission;
}
public async Task> GetAvailableMissionsAsync(Guid userId, CancellationToken ct = default)
{
var user = await _uow.Users.Query(u => u.Id == userId, null, u => u.Rank).FirstOrDefaultAsync(ct)
?? throw new KeyNotFoundException("User not found");
var missions = await _uow.Missions.Query(m => m.IsActive, null, m => m.MinRank).ToListAsync(ct);
var userOrder = user.Rank?.Order ?? int.MinValue;
return missions.Where(m => m.MinRank == null || m.MinRank.Order <= userOrder + 1);
}
public async Task UpdateStatusAsync(Guid userId, Guid missionId, MissionStatus status, string? submissionData, CancellationToken ct = default)
{
var mission = await _uow.Missions.Query(m => m.Id == missionId).FirstOrDefaultAsync(ct)
?? throw new KeyNotFoundException("Mission not found");
var userMission = await _uow.UserMissions.FindAsync(userId, missionId);
if (userMission == null)
{
userMission = new UserMission
{
UserId = userId,
MissionId = missionId,
Status = MissionStatus.Available
};
await _uow.UserMissions.AddAsync(userMission, ct);
}
userMission.Status = status;
userMission.SubmissionData = submissionData;
userMission.UpdatedAt = DateTime.UtcNow;
await _uow.SaveChangesAsync(ct);
await LogEventAsync(EventType.MissionStatusChanged, userId, new { missionId, status }, ct);
if (status == MissionStatus.Completed)
{
await _gamification.ApplyMissionCompletionAsync(userId, mission, ct);
await LogEventAsync(EventType.RewardGranted, userId, new { missionId, mission.ExperienceReward, mission.ManaReward }, ct);
}
return userMission;
}
private async Task LogEventAsync(EventType type, Guid? userId, object data, CancellationToken ct)
{
if (userId == null && type != EventType.MissionStatusChanged) return;
var evt = new EventLog
{
Type = type,
UserId = userId ?? Guid.Empty,
Data = JsonSerializer.Serialize(data)
};
await _uow.EventLogs.AddAsync(evt, ct);
await _uow.SaveChangesAsync(ct);
Log.Debug("Event {Type} logged {Data}", type, evt.Data);
}
}