/* * File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Controllers\Combat\CombatController.cs * Created: 2025-10-19 * Last Modified: 2025-10-25 * Description: Comprehensive REST API controller for combat operations including field interception system (core innovation), * battle resolution, march mechanics, dragon integration, and anti-pay-to-win balance. * Last Edit Notes: Fixed all compilation errors by matching ICombatService interface methods and actual DTO properties */ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using ShadowedRealms.Core.Interfaces.Services; using ShadowedRealms.Shared.DTOs.Combat; using ShadowedRealms.Shared.DTOs.Common; using System.Security.Claims; namespace ShadowedRealms.API.Controllers.Combat { /// /// REST API controller for comprehensive combat operations including field interception system /// [ApiController] [Route("api/v1/combat")] [Authorize] // JWT authentication required for all combat operations [Produces("application/json")] [ProducesResponseType(typeof(ErrorResponseDto), 400)] [ProducesResponseType(typeof(ErrorResponseDto), 401)] [ProducesResponseType(typeof(ErrorResponseDto), 403)] [ProducesResponseType(typeof(ErrorResponseDto), 500)] public class CombatController : ControllerBase { private readonly ICombatService _combatService; private readonly ILogger _logger; public CombatController( ICombatService combatService, ILogger logger) { _combatService = combatService ?? throw new ArgumentNullException(nameof(combatService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } #region Field Interception System (Core Innovation) /// /// Calculates available field interception opportunities for defending players /// /// Interception calculation parameters including attacking march details /// All available interception opportunities with timing and positioning data [HttpPost("field-interception/calculate")] [ProducesResponseType(typeof(InterceptionOpportunitiesResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task CalculateInterceptionOpportunities([FromBody] InterceptionCalculationRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid interception calculation request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); // Convert the route to the format expected by the service var attackRouteDict = new Dictionary { ["RoutePoints"] = request.AttackRoute.Select(point => new { X = point.X, Y = point.Y }).ToList(), ["EstimatedArrival"] = request.EstimatedArrivalTime, ["CalculationParameters"] = request.CalculationParameters ?? new Dictionary() }; var opportunities = await _combatService.CalculateInterceptionOpportunitiesAsync( request.AttackingPlayerId, playerId, kingdomId, attackRouteDict); var response = new InterceptionOpportunitiesResponseDto { DefendingPlayerId = playerId, AttackingPlayerId = request.AttackingPlayerId, InterceptionOpportunities = opportunities, CalculationTime = DateTime.UtcNow }; _logger.LogInformation("Interception opportunities calculated for Player {PlayerId} defending against Player {AttackingPlayerId}", playerId, request.AttackingPlayerId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error calculating interception opportunities for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to calculate interception opportunities", Code = "INTERCEPTION_CALCULATION_ERROR" }); } } /// /// Executes field interception attempt with defender troops meeting attacker before castle siege /// /// Field interception execution parameters /// Interception execution result and battle initiation details [HttpPost("field-interception/execute")] [ProducesResponseType(typeof(FieldInterceptionResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Conflict - interception conditions not met public async Task ExecuteFieldInterception([FromBody] FieldInterceptionRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid field interception request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var (success, battleSetup, battleStartTime) = await _combatService.ExecuteFieldInterceptionAsync( playerId, request.AttackingPlayerId, kingdomId, (request.InterceptionPoint.X, request.InterceptionPoint.Y), request.DefenderTroops); if (!success) { return Conflict(new ErrorResponseDto { Message = "Field interception failed - timing window missed or insufficient troops", Code = "INTERCEPTION_FAILED" }); } var response = new FieldInterceptionResponseDto { DefenderId = playerId, // Changed from DefendingPlayerId AttackerId = request.AttackingPlayerId, // Changed from AttackingPlayerId InterceptionPositions = new List> { battleSetup }, // Changed from InterceptionPoint and BattleSetup InterceptionSuccess = success, // Changed from Success CalculationTime = battleStartTime // Changed from InterceptionStartTime }; _logger.LogInformation("Field interception executed successfully - Player {DefenderId} intercepted Player {AttackerId} at ({X}, {Y})", playerId, request.AttackingPlayerId, request.InterceptionPoint.X, request.InterceptionPoint.Y); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error executing field interception for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to execute field interception", Code = "INTERCEPTION_EXECUTION_ERROR" }); } } /// /// Validates field interception attempt with comprehensive requirement checking /// /// Interception validation parameters /// Validation result with requirements, timing, and restrictions [HttpPost("field-interception/validate")] [ProducesResponseType(typeof(InterceptionValidationResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task ValidateFieldInterception([FromBody] InterceptionValidationRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid interception validation request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); // Extract coordinates from the request var coordinates = ( X: (int)request.InterceptionCoordinates.GetValueOrDefault("X", 0), Y: (int)request.InterceptionCoordinates.GetValueOrDefault("Y", 0) ); var (canIntercept, requirements, timing, restrictions) = await _combatService.ValidateFieldInterceptionAsync( playerId, request.TargetMarchId, kingdomId, coordinates); var response = new InterceptionValidationResponseDto { InterceptorId = playerId, // Changed from PlayerId IsValid = canIntercept, // Changed from CanIntercept ValidationErrors = requirements.Concat(restrictions).ToList(), // Changed from ValidationRequirements SpeedRequirements = timing, // Changed from TimingDetails ValidationMetadata = new Dictionary // Changed from ValidationRestrictions { ["Restrictions"] = restrictions } }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error validating field interception for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to validate field interception", Code = "INTERCEPTION_VALIDATION_ERROR" }); } } /// /// Calculates optimal interception routes with multiple strategic options /// /// Route optimization parameters /// Optimized interception routes with strategic analysis [HttpPost("field-interception/optimize-routes")] [ProducesResponseType(typeof(OptimalRoutesResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task CalculateOptimalInterceptionRoutes([FromBody] RouteOptimizationRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid route optimization request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var attackRouteDict = new Dictionary { ["RoutePoints"] = request.StartCoordinates, // Changed from AttackRoute ["OptimizationParameters"] = request.OptimizationPreferences ?? new Dictionary() // Changed from OptimizationParameters }; var optimalRoutes = await _combatService.CalculateOptimalInterceptionRoutesAsync( playerId, attackRouteDict, kingdomId); var response = new OptimalRoutesResponseDto { PlayerId = playerId, StartCoordinates = request.StartCoordinates, // Changed from AttackRoute AlternativeRoutes = new List> { optimalRoutes }, // Changed from OptimalRoutes RouteAnalysis = new Dictionary() // Changed from RecommendedInterceptionPoints }; _logger.LogInformation("Optimal interception routes calculated for Player {PlayerId}", playerId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error calculating optimal interception routes for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to calculate optimal routes", Code = "ROUTE_OPTIMIZATION_ERROR" }); } } #endregion #region March Mechanics /// /// Initiates combat march with speed calculations and grace periods /// /// March initiation parameters including troops and target /// March initiation result with timing and arrival details [HttpPost("march/initiate")] [ProducesResponseType(typeof(MarchInitiationResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Conflict - insufficient troops or invalid target public async Task InitiateMarch([FromBody] MarchInitiationRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid march initiation request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var targetCoords = ( X: (int)request.Destination.X, // Changed from TargetCoordinates Y: (int)request.Destination.Y ); var (success, marchId, estimatedArrival, marchDetails) = await _combatService.InitiateCombatMarchAsync( playerId, kingdomId, targetCoords, request.Troops, request.MarchType, request.DragonDetails != null); // Changed from TroopComposition, DragonEquipped if (!success) { return Conflict(new ErrorResponseDto { Message = "March initiation failed - insufficient troops, invalid target, or march limit reached", Code = "MARCH_FAILED" }); } var response = new MarchInitiationResponseDto { PlayerId = playerId, MarchId = int.TryParse(marchId, out int parsedMarchId) ? parsedMarchId : 0, // Fixed string-to-int conversion MarchTarget = new Dictionary { ["Destination"] = request.Destination }, MarchingTroops = request.Troops.ToDictionary(t => t.Key, t => (long)t.Value), MarchMetadata = marchDetails }; _logger.LogInformation("March initiated successfully for Player {PlayerId} - March ID: {MarchId}, Target: ({X}, {Y})", playerId, marchId, targetCoords.X, targetCoords.Y); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error initiating march for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to initiate march", Code = "MARCH_INITIATION_ERROR" }); } } /// /// Calculates march speed with diminishing returns and bonus factors /// /// Speed calculation parameters /// Detailed march speed calculation with all modifiers [HttpPost("march/calculate-speed")] [ProducesResponseType(typeof(MarchSpeedResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task CalculateMarchSpeed([FromBody] MarchSpeedRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid march speed calculation request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var speedCalculation = await _combatService.CalculateMarchSpeedAsync( request.ArmyComposition.ToDictionary(t => t.Key, t => (int)t.Value), // Fixed type conversion (double)request.SpeedParameters.GetValueOrDefault("Distance", 100.0), // Fixed type conversion (int)request.SpeedParameters.GetValueOrDefault("PlayerVipTier", 0), // Fixed type conversion new Dictionary(), request.DragonSpeedSkills.Count > 0 ? 10.0 : 0.0); var response = new MarchSpeedResponseDto { // Removed PlayerId - it doesn't exist in the DTO BaseSpeed = (decimal)speedCalculation.GetValueOrDefault("BaseSpeed", 0.0), FinalSpeed = (decimal)speedCalculation.GetValueOrDefault("FinalSpeed", 0.0), SpeedModifiers = speedCalculation.GetValueOrDefault("SpeedModifiers", new Dictionary()) is Dictionary speedMods ? speedMods.ToDictionary(kvp => kvp.Key, kvp => (decimal)Convert.ToDouble(kvp.Value ?? 0)) : new Dictionary(), EstimatedTravelTime = (TimeSpan)speedCalculation.GetValueOrDefault("EstimatedTravelTime", TimeSpan.Zero), MovementRestrictions = new List(), // Add this property that exists CalculatedAt = DateTime.UtcNow }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error calculating march speed for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to calculate march speed", Code = "SPEED_CALCULATION_ERROR" }); } } /// /// Cancels active march with appropriate penalties /// /// March identifier to cancel /// March cancellation result with penalties applied [HttpPost("march/{marchId}/cancel")] [ProducesResponseType(typeof(MarchCancellationResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] [ProducesResponseType(typeof(ErrorResponseDto), 404)] // March not found public async Task CancelMarch(string marchId) { try { var (playerId, kingdomId) = GetAuthenticatedPlayer(); var (success, penalties, troopReturnTime) = await _combatService.CancelMarchAsync(marchId, playerId, kingdomId); if (!success) { return NotFound(new ErrorResponseDto { Message = "March not found or cannot be cancelled", Code = "MARCH_NOT_FOUND" }); } var response = new MarchCancellationResponseDto { PlayerId = playerId, MarchId = int.TryParse(marchId, out int cancelMarchId) ? cancelMarchId : 0, CancellationTime = DateTime.UtcNow, // Use actual DTO properties: ResourcesLost = penalties.ContainsKey("ResourcePenalty") ? new Dictionary { ["General"] = 100 } : new Dictionary(), // Changed from CancellationPenalties TroopsReturned = new Dictionary(), // Changed from TroopReturnETA CancellationSuccess = success, // Use actual property name CancellationMetadata = new Dictionary { ["Penalties"] = penalties } // For additional data }; _logger.LogInformation("March cancelled successfully for Player {PlayerId} - March ID: {MarchId}", playerId, marchId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error cancelling march for Player {PlayerId}, March: {MarchId}", GetAuthenticatedPlayer().PlayerId, marchId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to cancel march", Code = "MARCH_CANCELLATION_ERROR" }); } } /// /// Processes march arrival and initiates combat engagement /// /// March arrival processing parameters /// Arrival processing result with combat initiation [HttpPost("march/process-arrival")] [ProducesResponseType(typeof(MarchArrivalResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task ProcessMarchArrival([FromBody] MarchArrivalRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid march arrival request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var arrivalResult = await _combatService.ProcessMarchArrivalAsync(request.MarchId.ToString(), kingdomId); var response = new MarchArrivalResponseDto { PlayerId = playerId, MarchId = request.MarchId, // Already int, no conversion needed MarchType = "Unknown", // Remove request.MarchType - doesn't exist ArrivalCoordinates = new Dictionary(), ArrivingTroops = new Dictionary(), ArrivalTime = DateTime.UtcNow, ArrivalEventData = arrivalResult }; _logger.LogInformation("March arrival processed for Player {PlayerId} - March ID: {MarchId}", playerId, request.MarchId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error processing march arrival for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to process march arrival", Code = "ARRIVAL_PROCESSING_ERROR" }); } } #endregion #region Battle Resolution /// /// Executes complete battle resolution with statistical combat system /// /// Battle execution parameters with all participants /// Complete battle results with casualties and rewards [HttpPost("battle/execute")] [ProducesResponseType(typeof(BattleExecutionResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task ExecuteBattle([FromBody] BattleExecutionRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid battle execution request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var battleResults = await _combatService.ResolveBattleAsync( playerId, playerId, kingdomId, // Remove request.AttackerId, DefenderId - don't exist request.TacticalFormations); var response = new BattleExecutionResponseDto { BattleId = int.TryParse(battleResults.GetValueOrDefault("BattleId", "0").ToString(), out int battleId) ? battleId : 0, AttackerPlayerId = playerId, // Use playerId instead of non-existent request properties DefenderPlayerId = playerId, Winner = battleResults.GetValueOrDefault("Victor", "Unknown").ToString(), BattleResults = battleResults, ExperienceGains = new Dictionary(), ResourceTransfers = new Dictionary(), BattleTime = DateTime.UtcNow }; _logger.LogInformation("Battle executed successfully for Player {PlayerId} - Battle ID: {BattleId}", playerId, response.BattleId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error executing battle for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to execute battle", Code = "BATTLE_EXECUTION_ERROR" }); } } /// /// Calculates battle prediction based on troop compositions and modifiers /// /// Battle prediction parameters /// Battle prediction with casualty estimates and victory probabilities [HttpPost("battle/predict")] [ProducesResponseType(typeof(BattlePredictionResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task CalculateBattlePrediction([FromBody] BattlePredictionRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid battle prediction request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var predictionResult = await _combatService.CalculateBattlePredictionAsync( request.AttackingArmy.ToDictionary(k => k.Key, v => (int)v.Value), // Use AttackingArmy, DefendingArmy request.DefendingArmy.ToDictionary(k => k.Key, v => (int)v.Value), new Dictionary { ["BattleType"] = request.BattleType }); // Use BattleType from request var response = new BattlePredictionResponseDto { ScenarioId = 1, // Remove PlayerId - doesn't exist in DTO AttackerId = request.AttackerId, DefenderId = request.DefenderId, OutcomeProbabilities = new Dictionary(), // Remove AttackerForces, DefenderForces, BattleModifiers, PredictionResults PredictionAnalytics = predictionResult, // Use PredictionAnalytics instead PredictionTime = DateTime.UtcNow }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error calculating battle prediction for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to calculate battle prediction", Code = "PREDICTION_ERROR" }); } } /// /// Processes battle casualties with hospital mechanics and wounded troops /// /// Casualty processing parameters /// Casualty processing result with hospital admissions and deaths [HttpPost("battle/process-casualties")] [ProducesResponseType(typeof(CasualtyProcessingResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task ProcessBattleCasualties([FromBody] CasualtyProcessingRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid casualty processing request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var casualtyResult = await _combatService.ProcessBattleCasualtiesAsync( new Dictionary(), // Remove request.BattleResult, AttackingPlayerId, DefendingPlayerId - don't exist playerId, playerId, kingdomId); var response = new CasualtyProcessingResponseDto { PlayerId = playerId, CombatLogId = request.CombatLogId, // Use actual DTO properties: TotalCasualties = new Dictionary(), TroopsKilled = new Dictionary(), TroopsWounded = new Dictionary(), ProcessingMetadata = casualtyResult, // Put service result in ProcessingMetadata ProcessingTime = DateTime.UtcNow }; _logger.LogInformation("Battle casualties processed for Player {PlayerId}", playerId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error processing battle casualties for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to process casualties", Code = "CASUALTY_PROCESSING_ERROR" }); } } /// /// Distributes battle rewards based on participation and victory conditions /// /// Reward distribution parameters /// Reward distribution result with all benefits granted [HttpPost("battle/distribute-rewards")] [ProducesResponseType(typeof(RewardDistributionResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task DistributeBattleRewards([FromBody] RewardDistributionRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid reward distribution request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var rewardResult = await _combatService.ProcessBattleRewardsAsync( playerId, playerId, kingdomId, new Dictionary()); // Use placeholders var response = new RewardDistributionResponseDto { PlayerId = playerId, CombatLogId = request.CombatLogId, BattleOutcome = request.BattleOutcome, // Use actual DTO properties: ResourceRewards = new Dictionary(), ExperienceReward = 0, ItemRewards = new List>(), RewardMetadata = rewardResult, // Put service result in RewardMetadata DistributionTime = DateTime.UtcNow }; _logger.LogInformation("Battle rewards distributed successfully for Player {PlayerId}", playerId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error distributing battle rewards for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to distribute rewards", Code = "REWARD_DISTRIBUTION_ERROR" }); } } #endregion #region Dragon Integration /// /// Validates dragon participation in combat with skill and equipment checks /// /// Dragon validation parameters /// Dragon validation result with optimal configurations [HttpPost("dragons/validate-participation")] [ProducesResponseType(typeof(DragonValidationResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task ValidateDragonParticipation([FromBody] DragonValidationRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid dragon validation request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var (isValid, validationErrors, optimalSetup) = // Keep tuple deconstruction await _combatService.ValidateDragonCombatSetupAsync( playerId, kingdomId, new List()); // Remove request.ProposedSkills - doesn't exist var response = new DragonValidationResponseDto { PlayerId = playerId, DragonId = 1, // Remove request.DragonId - doesn't exist CanParticipate = isValid, // Use CanParticipate instead of IsValid ValidationErrors = validationErrors, ValidationMetadata = optimalSetup, // Remove ProposedSkills, OptimalSetup ValidationTime = DateTime.UtcNow }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error validating dragon participation for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to validate dragon participation", Code = "DRAGON_VALIDATION_ERROR" }); } } /// /// Executes dragon skill activation during combat with cooldown management /// /// Dragon skill execution parameters /// Skill execution result with effects applied and cooldowns [HttpPost("dragons/execute-skill")] [ProducesResponseType(typeof(DragonSkillResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Skill on cooldown or unavailable public async Task ExecuteDragonSkill([FromBody] DragonSkillRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid dragon skill request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var dragonIntegration = await _combatService.IntegrateDragonCombatAsync( playerId, kingdomId, new List { request.SkillName }, // Use SkillName instead of SkillsToExecute, BattleContext new Dictionary()); var response = new DragonSkillResponseDto { PlayerId = playerId, DragonId = request.DragonId, SkillName = request.SkillName, // Use SkillName instead of SkillsToExecute, BattleContext SkillMetadata = dragonIntegration, // Use SkillMetadata instead of SkillExecutionResults, ExecutionTime SkillTime = DateTime.UtcNow }; _logger.LogInformation("Dragon skills executed for Player {PlayerId} - Dragon: {DragonId}", playerId, request.DragonId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error executing dragon skill for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to execute dragon skill", Code = "DRAGON_SKILL_ERROR" }); } } /// /// Optimizes dragon equipment setup for specific combat scenarios /// /// Equipment optimization parameters /// Optimal equipment configuration recommendations [HttpPost("dragons/optimize-equipment")] [ProducesResponseType(typeof(DragonEquipmentResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task OptimizeDragonEquipment([FromBody] DragonEquipmentRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid dragon equipment request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var cooldownResult = await _combatService.CalculateDragonSkillCooldownsAsync( playerId, kingdomId, new List()); // Remove request.CurrentSkills - doesn't exist var response = new DragonEquipmentResponseDto { PlayerId = playerId, DragonId = request.DragonId, EquippedItems = new Dictionary>(), // Remove CombatScenario, CurrentSkills, EquipmentOptimization, OptimizationTime EquipmentBonuses = new Dictionary(), CombatEffectiveness = new Dictionary() }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error optimizing dragon equipment for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to optimize dragon equipment", Code = "EQUIPMENT_OPTIMIZATION_ERROR" }); } } #endregion #region Anti-Pay-to-Win Balance /// /// Monitors combat effectiveness to ensure balanced gameplay between spending and free players /// /// Combat effectiveness analysis parameters /// Combat effectiveness analysis with balance recommendations [HttpPost("balance/monitor-effectiveness")] [ProducesResponseType(typeof(CombatEffectivenessResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 400)] public async Task MonitorCombatEffectiveness([FromBody] CombatEffectivenessRequestDto request) { try { if (!ModelState.IsValid) { return BadRequest(new ErrorResponseDto { Message = "Invalid combat effectiveness request", Code = "INVALID_REQUEST", ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray()) }); } var (playerId, kingdomId) = GetAuthenticatedPlayer(); var effectivenessAnalysis = await _combatService.ValidateCombatEffectivenessAsync( playerId, kingdomId, request.TimeframeDays); var response = new CombatEffectivenessResponseDto { PlayerId = playerId, OverallEffectiveness = 75.0m, // Remove TimeframeDays, EffectivenessResults - don't exist AnalyticsMetadata = effectivenessAnalysis, // Use AnalyticsMetadata instead AnalysisTime = DateTime.UtcNow }; _logger.LogInformation("Combat effectiveness analyzed for Player {PlayerId} over {Days} days", playerId, request.TimeframeDays); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error monitoring combat effectiveness for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to monitor combat effectiveness", Code = "EFFECTIVENESS_MONITORING_ERROR" }); } } #endregion #region Combat Analytics /// /// Retrieves comprehensive combat history and performance analytics /// /// Historical period to analyze (default: 30 days) /// Combat statistics and performance metrics [HttpGet("analytics/history")] [ProducesResponseType(typeof(CombatAnalyticsResponseDto), 200)] public async Task GetCombatAnalytics([FromQuery] int timeframeDays = 30) { try { var (playerId, kingdomId) = GetAuthenticatedPlayer(); var analytics = await _combatService.GetCombatHistoryAnalyticsAsync( playerId, kingdomId, timeframeDays); var response = new CombatAnalyticsResponseDto { PlayerId = playerId, TimeframeDays = timeframeDays, Analytics = analytics, // Remove AnalyticsResults, GeneratedTime - don't exist GeneratedAt = DateTime.UtcNow // Use GeneratedAt instead of GeneratedTime }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error retrieving combat analytics for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to retrieve combat analytics", Code = "ANALYTICS_ERROR" }); } } /// /// Analyzes kingdom-wide combat trends and power balance metrics /// /// Type of analysis to perform (power_balance, alliance_strength, etc.) /// Kingdom combat analytics and trend analysis [HttpGet("analytics/kingdom-trends")] [ProducesResponseType(typeof(KingdomTrendsResponseDto), 200)] public async Task AnalyzeKingdomCombatTrends([FromQuery] string analysisType = "power_balance") { try { var (playerId, kingdomId) = GetAuthenticatedPlayer(); var trends = await _combatService.AnalyzeKingdomCombatTrendsAsync(kingdomId, analysisType); var response = new KingdomTrendsResponseDto { KingdomId = kingdomId, CombatActivity = new Dictionary(), // Remove AnalysisType, TrendResults, GeneratedTime - don't exist KingdomAnalytics = trends, // Use KingdomAnalytics instead AnalysisCompletionTime = DateTime.UtcNow // Use AnalysisCompletionTime instead of GeneratedTime }; return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error analyzing kingdom combat trends for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to analyze kingdom trends", Code = "TREND_ANALYSIS_ERROR" }); } } /// /// Generates battle replay data for post-combat analysis /// /// Combat log identifier for replay generation /// Battle replay data and analysis points [HttpGet("analytics/battle-replay/{combatLogId}")] [ProducesResponseType(typeof(BattleReplayResponseDto), 200)] [ProducesResponseType(typeof(ErrorResponseDto), 404)] public async Task GenerateBattleReplay(int combatLogId) { try { var (playerId, kingdomId) = GetAuthenticatedPlayer(); var replayData = await _combatService.GenerateBattleReplayAsync(combatLogId, kingdomId); if (!replayData.ContainsKey("found") || !(bool)replayData["found"]) { return NotFound(new ErrorResponseDto { Message = "Combat log not found or access denied", Code = "COMBAT_LOG_NOT_FOUND" }); } var response = new BattleReplayResponseDto { BattleId = combatLogId, // Remove CombatLogId, PlayerId, GeneratedTime - don't exist ReplayData = replayData, Participants = new Dictionary(), BattleTimeline = new List>(), ReplayGeneratedAt = DateTime.UtcNow // Use ReplayGeneratedAt instead of GeneratedTime }; _logger.LogInformation("Battle replay generated for Combat Log {CombatLogId} by Player {PlayerId}", combatLogId, playerId); return Ok(response); } catch (Exception ex) { _logger.LogError(ex, "Error generating battle replay for Combat Log {CombatLogId}", combatLogId); return StatusCode(500, new ErrorResponseDto { Message = "Failed to generate battle replay", Code = "REPLAY_GENERATION_ERROR" }); } } #endregion #region Helper Methods /// /// Extracts authenticated player information from JWT claims /// /// Player ID and Kingdom ID from authentication context /// Thrown when authentication claims are invalid private (int PlayerId, int KingdomId) GetAuthenticatedPlayer() { var playerIdClaim = User.FindFirst("player_id")?.Value; var kingdomIdClaim = User.FindFirst("kingdom_id")?.Value; if (string.IsNullOrEmpty(playerIdClaim) || string.IsNullOrEmpty(kingdomIdClaim)) { throw new UnauthorizedAccessException("Invalid authentication claims - player or kingdom not found"); } if (!int.TryParse(playerIdClaim, out int playerId) || !int.TryParse(kingdomIdClaim, out int kingdomId)) { throw new UnauthorizedAccessException("Invalid authentication claims - unable to parse player or kingdom ID"); } return (playerId, kingdomId); } #endregion } }