using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using RR3CommunityServer.Data; using RR3CommunityServer.Models; namespace RR3CommunityServer.Controllers; [ApiController] [Route("synergy/events")] public class EventsController : ControllerBase { private readonly RR3DbContext _context; private readonly ILogger _logger; public EventsController(RR3DbContext context, ILogger logger) { _context = context; _logger = logger; } // GET /synergy/events/active [HttpGet("active")] public async Task GetActiveEvents([FromQuery] string? synergyId) { try { _logger.LogInformation("Getting active events for player: {SynergyId}", synergyId ?? "unknown"); // Get all active events var events = await _context.Events .Where(e => e.Active) .OrderBy(e => e.SeriesOrder) .ThenBy(e => e.EventOrder) .ToListAsync(); // If synergyId provided, get player's completion status Dictionary? completions = null; if (!string.IsNullOrEmpty(synergyId)) { var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId); if (user != null) { completions = await _context.EventCompletions .Where(ec => ec.UserId == user.Id) .ToDictionaryAsync(ec => ec.EventId, ec => ec); } } var response = events.Select(e => new EventListDto { EventId = e.Id, EventCode = e.EventCode, Name = e.Name, SeriesName = e.SeriesName, Track = e.Track, Type = e.EventType, RequiredPR = e.RequiredPR, GoldReward = e.GoldReward, CashReward = e.CashReward, XPReward = e.XPReward, IsCompleted = completions?.ContainsKey(e.Id) ?? false, BestTime = completions?.GetValueOrDefault(e.Id)?.BestTime, TimesCompleted = completions?.GetValueOrDefault(e.Id)?.CompletionCount ?? 0 }).ToList(); return Ok(new SynergyResponse> { resultCode = 0, message = "Success", data = response }); } catch (Exception ex) { _logger.LogError(ex, "Error getting active events"); return StatusCode(500, new SynergyResponse { resultCode = -1, message = "Internal server error" }); } } // GET /synergy/events/{eventId} [HttpGet("{eventId}")] public async Task GetEventDetails(int eventId, [FromQuery] string? synergyId) { try { _logger.LogInformation("Getting event details: {EventId} for {SynergyId}", eventId, synergyId ?? "unknown"); var eventData = await _context.Events.FindAsync(eventId); if (eventData == null || !eventData.Active) { return NotFound(new SynergyResponse { resultCode = 404, message = "Event not found or inactive" }); } // Get player's stats if provided EventCompletion? completion = null; if (!string.IsNullOrEmpty(synergyId)) { var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId); if (user != null) { completion = await _context.EventCompletions .FirstOrDefaultAsync(ec => ec.UserId == user.Id && ec.EventId == eventId); } } var response = new EventDetailsDto { EventId = eventData.Id, EventCode = eventData.EventCode, Name = eventData.Name, SeriesName = eventData.SeriesName, Track = eventData.Track, EventType = eventData.EventType, Laps = eventData.Laps, RequiredPR = eventData.RequiredPR, RequiredCarClass = eventData.RequiredCarClass, GoldReward = eventData.GoldReward, CashReward = eventData.CashReward, XPReward = eventData.XPReward, TargetTime = eventData.TargetTime, IsCompleted = completion != null, BestTime = completion?.BestTime, TimesCompleted = completion?.CompletionCount ?? 0, LastCompleted = completion?.LastCompletedAt }; return Ok(new SynergyResponse { resultCode = 0, message = "Success", data = response }); } catch (Exception ex) { _logger.LogError(ex, "Error getting event details for event {EventId}", eventId); return StatusCode(500, new SynergyResponse { resultCode = -1, message = "Internal server error" }); } } // POST /synergy/events/{eventId}/start [HttpPost("{eventId}/start")] public async Task StartEvent(int eventId, [FromBody] EventStartRequest request) { try { _logger.LogInformation("Starting event {EventId} for {SynergyId}", eventId, request.SynergyId); var eventData = await _context.Events.FindAsync(eventId); if (eventData == null || !eventData.Active) { return NotFound(new { error = "Event not found or inactive" }); } var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == request.SynergyId); if (user == null) { return NotFound(new { error = "User not found" }); } // Create or update event attempt var attempt = await _context.EventAttempts .FirstOrDefaultAsync(ea => ea.UserId == user.Id && ea.EventId == eventId && !ea.Completed); if (attempt == null) { attempt = new EventAttempt { UserId = user.Id, EventId = eventId, StartedAt = DateTime.UtcNow, Completed = false }; _context.EventAttempts.Add(attempt); } else { attempt.StartedAt = DateTime.UtcNow; } await _context.SaveChangesAsync(); return Ok(new SynergyResponse { resultCode = 0, message = "Event started", data = new { attemptId = attempt.Id, eventId = eventData.Id, startTime = attempt.StartedAt } }); } catch (Exception ex) { _logger.LogError(ex, "Error starting event {EventId}", eventId); return StatusCode(500, new { error = "Internal server error" }); } } // POST /synergy/events/{eventId}/complete [HttpPost("{eventId}/complete")] public async Task CompleteEvent(int eventId, [FromBody] EventCompleteRequest request) { try { _logger.LogInformation("Completing event {EventId} for {SynergyId}, time: {Time}", eventId, request.SynergyId, request.TimeSeconds); var eventData = await _context.Events.FindAsync(eventId); if (eventData == null || !eventData.Active) { return NotFound(new { error = "Event not found or inactive" }); } var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == request.SynergyId); if (user == null) { return NotFound(new { error = "User not found" }); } // Get or create completion record var completion = await _context.EventCompletions .FirstOrDefaultAsync(ec => ec.UserId == user.Id && ec.EventId == eventId); bool isFirstCompletion = completion == null; bool isNewBestTime = false; double? previousBest = completion?.BestTime; if (completion == null) { completion = new EventCompletion { UserId = user.Id, EventId = eventId, BestTime = request.TimeSeconds, CompletionCount = 1, FirstCompletedAt = DateTime.UtcNow, LastCompletedAt = DateTime.UtcNow }; _context.EventCompletions.Add(completion); isNewBestTime = true; } else { completion.CompletionCount++; completion.LastCompletedAt = DateTime.UtcNow; if (request.TimeSeconds < completion.BestTime) { completion.BestTime = request.TimeSeconds; isNewBestTime = true; } } // Award rewards (only full rewards on first completion) int goldEarned = isFirstCompletion ? eventData.GoldReward : (eventData.GoldReward / 4); int cashEarned = isFirstCompletion ? eventData.CashReward : (eventData.CashReward / 2); int xpEarned = isFirstCompletion ? eventData.XPReward : (eventData.XPReward / 2); // Bonus gold for new best time (if not first completion) if (!isFirstCompletion && isNewBestTime) { goldEarned += 25; } user.Gold += goldEarned; user.Cash += cashEarned; user.Level += xpEarned / 1000; // Simple XP to level conversion // Mark attempt as completed var attempt = await _context.EventAttempts .FirstOrDefaultAsync(ea => ea.UserId == user.Id && ea.EventId == eventId && !ea.Completed); if (attempt != null) { attempt.Completed = true; attempt.CompletedAt = DateTime.UtcNow; attempt.TimeSeconds = request.TimeSeconds; } await _context.SaveChangesAsync(); return Ok(new SynergyResponse { resultCode = 0, message = "Event completed", data = new EventCompleteResponse { IsFirstCompletion = isFirstCompletion, IsNewBestTime = isNewBestTime, BestTime = completion.BestTime, PreviousBest = previousBest, Improvement = previousBest.HasValue ? previousBest.Value - request.TimeSeconds : 0, GoldEarned = goldEarned, CashEarned = cashEarned, XPEarned = xpEarned, TotalCompletions = completion.CompletionCount, NewBalance = new { gold = user.Gold, cash = user.Cash, level = user.Level } } }); } catch (Exception ex) { _logger.LogError(ex, "Error completing event {EventId}", eventId); return StatusCode(500, new { error = "Internal server error" }); } } } // DTOs public class EventListDto { public int EventId { get; set; } public string EventCode { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string SeriesName { get; set; } = string.Empty; public string Track { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public int RequiredPR { get; set; } public int GoldReward { get; set; } public int CashReward { get; set; } public int XPReward { get; set; } public bool IsCompleted { get; set; } public double? BestTime { get; set; } public int TimesCompleted { get; set; } } public class EventDetailsDto : EventListDto { public string EventType { get; set; } = string.Empty; public int Laps { get; set; } public string? RequiredCarClass { get; set; } public double? TargetTime { get; set; } public DateTime? LastCompleted { get; set; } } public class EventStartRequest { public string SynergyId { get; set; } = string.Empty; } public class EventCompleteRequest { public string SynergyId { get; set; } = string.Empty; public double TimeSeconds { get; set; } } public class EventCompleteResponse { public bool IsFirstCompletion { get; set; } public bool IsNewBestTime { get; set; } public double BestTime { get; set; } public double? PreviousBest { get; set; } public double Improvement { get; set; } public int GoldEarned { get; set; } public int CashEarned { get; set; } public int XPEarned { get; set; } public int TotalCompletions { get; set; } public object? NewBalance { get; set; } }