diff --git a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Alliance/AllianceController.cs b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Alliance/AllianceController.cs
new file mode 100644
index 0000000..6b5ddfc
--- /dev/null
+++ b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Alliance/AllianceController.cs
@@ -0,0 +1,1490 @@
+/*
+ * File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Controllers\Alliance\AllianceController.cs
+ * Created: 2025-10-19
+ * Last Modified: 2025-10-19
+ * Description: Comprehensive REST API controller for alliance operations including coalition mechanics (core innovation),
+ * research trees, territory management, democratic systems, and resource sharing.
+ * Last Edit Notes: Initial implementation exposing all AllianceService functionality through RESTful endpoints
+ * with proper authentication, validation, and error handling.
+ */
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ShadowedRealms.Core.Interfaces.Services;
+using ShadowedRealms.Shared.DTOs.Alliance;
+using ShadowedRealms.Shared.DTOs.Common;
+using System.Security.Claims;
+
+namespace ShadowedRealms.API.Controllers.Alliance
+{
+ ///
+ /// REST API controller for comprehensive alliance management including coalition mechanics
+ ///
+ [ApiController]
+ [Route("api/v1/alliances")]
+ [Authorize] // JWT authentication required for all alliance operations
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 401)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 500)]
+ public class AllianceController : ControllerBase
+ {
+ private readonly IAllianceService _allianceService;
+ private readonly ILogger _logger;
+
+ public AllianceController(
+ IAllianceService allianceService,
+ ILogger logger)
+ {
+ _allianceService = allianceService ?? throw new ArgumentNullException(nameof(allianceService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ #region Alliance Information and Management
+
+ ///
+ /// Retrieves comprehensive alliance information including members, research, and territories
+ ///
+ /// Alliance identifier (optional - defaults to current player's alliance)
+ /// Complete alliance information and status
+ [HttpGet("{allianceId?}")]
+ [ProducesResponseType(typeof(AllianceInfoResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 404)]
+ public async Task GetAllianceInfo(int? allianceId = null)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ // Use player's current alliance if no specific alliance requested
+ var targetAllianceId = allianceId ?? await GetPlayerAllianceId(playerId, kingdomId);
+
+ if (targetAllianceId == 0)
+ {
+ return NotFound(new ErrorResponseDto
+ {
+ Message = "Player not in alliance or alliance not found",
+ Code = "ALLIANCE_NOT_FOUND"
+ });
+ }
+
+ var allianceInfo = await _allianceService.GetAllianceInfoAsync(targetAllianceId, kingdomId);
+
+ var response = new AllianceInfoResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = targetAllianceId,
+ AllianceInfo = allianceInfo,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Alliance info retrieved for Alliance {AllianceId} by Player {PlayerId}",
+ targetAllianceId, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving alliance information for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve alliance information",
+ Code = "ALLIANCE_INFO_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Creates new alliance with democratic leadership structure
+ ///
+ /// Alliance creation parameters
+ /// Alliance creation result with initial setup
+ [HttpPost("create")]
+ [ProducesResponseType(typeof(AllianceCreationResponseDto), 201)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Player already in alliance
+ public async Task CreateAlliance([FromBody] AllianceCreationRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid alliance creation 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, allianceId, leadershipRoles, initialBenefits) =
+ await _allianceService.CreateAllianceAsync(
+ playerId, kingdomId, request.AllianceName, request.AllianceTag,
+ request.Description, request.InitialSettings);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Alliance creation failed - player already in alliance or name/tag taken",
+ Code = "CREATION_FAILED"
+ });
+ }
+
+ var response = new AllianceCreationResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ LeadershipRoles = leadershipRoles,
+ InitialBenefits = initialBenefits,
+ CreationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Alliance created successfully by Player {PlayerId} - Alliance ID: {AllianceId}, Name: '{Name}'",
+ playerId, allianceId, request.AllianceName);
+
+ return CreatedAtAction(nameof(GetAllianceInfo), new { allianceId = allianceId }, response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error creating alliance for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to create alliance",
+ Code = "ALLIANCE_CREATION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Updates alliance settings and configuration
+ ///
+ /// Alliance to update
+ /// Alliance update parameters
+ /// Alliance update result
+ [HttpPut("{allianceId}/settings")]
+ [ProducesResponseType(typeof(AllianceUpdateResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)] // Insufficient permissions
+ public async Task UpdateAllianceSettings(int allianceId, [FromBody] AllianceUpdateRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid alliance update 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, updatedSettings, permissionsRequired) =
+ await _allianceService.UpdateAllianceSettingsAsync(
+ playerId, allianceId, kingdomId, request.SettingUpdates);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Alliance update failed - insufficient permissions or invalid settings",
+ Code = "UPDATE_FAILED",
+ Details = new Dictionary { ["requiredPermissions"] = permissionsRequired }
+ });
+ }
+
+ var response = new AllianceUpdateResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ UpdatedSettings = updatedSettings,
+ UpdateTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Alliance settings updated for Alliance {AllianceId} by Player {PlayerId}",
+ allianceId, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error updating alliance settings for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to update alliance settings",
+ Code = "SETTINGS_UPDATE_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Coalition System (Core Innovation)
+
+ ///
+ /// Forms coalition between multiple alliances for KvK events
+ ///
+ /// Coalition formation parameters
+ /// Coalition formation result with coordination details
+ [HttpPost("coalition/form")]
+ [ProducesResponseType(typeof(CoalitionFormationResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)] // Insufficient authority
+ public async Task FormCoalition([FromBody] CoalitionFormationRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid coalition formation 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, coalitionId, coordinationStructure, memberAlliances) =
+ await _allianceService.FormCoalitionAsync(
+ playerId, kingdomId, request.ParticipatingAlliances,
+ request.CoalitionPurpose, request.CoordinationRules);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Coalition formation failed - insufficient authority or alliance conflicts",
+ Code = "COALITION_FORMATION_FAILED"
+ });
+ }
+
+ var response = new CoalitionFormationResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ CoalitionId = coalitionId,
+ CoordinationStructure = coordinationStructure,
+ MemberAlliances = memberAlliances,
+ FormationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Coalition formed successfully by Player {PlayerId} - Coalition ID: {CoalitionId}",
+ playerId, coalitionId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error forming coalition for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to form coalition",
+ Code = "COALITION_FORMATION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Manages coalition operations while preserving individual alliance identity
+ ///
+ /// Coalition to manage
+ /// Coalition management parameters
+ /// Coalition management result with operation status
+ [HttpPost("coalition/{coalitionId}/manage")]
+ [ProducesResponseType(typeof(CoalitionManagementResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ManageCoalition(string coalitionId, [FromBody] CoalitionManagementRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid coalition management request",
+ Code = "INVALID_REQUEST",
+ ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray())
+ });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var operationResults = await _allianceService.ManageCoalitionOperationsAsync(
+ playerId, coalitionId, kingdomId, request.Operations, request.CoordinationUpdates);
+
+ var response = new CoalitionManagementResponseDto
+ {
+ PlayerId = playerId,
+ CoalitionId = coalitionId,
+ OperationResults = operationResults,
+ ManagementTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Coalition operations managed for Coalition {CoalitionId} by Player {PlayerId}",
+ coalitionId, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error managing coalition operations for Coalition {CoalitionId}", coalitionId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to manage coalition",
+ Code = "COALITION_MANAGEMENT_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Dissolves coalition with benefit distribution
+ ///
+ /// Coalition to dissolve
+ /// Coalition dissolution parameters
+ /// Coalition dissolution result with benefit distribution
+ [HttpPost("coalition/{coalitionId}/dissolve")]
+ [ProducesResponseType(typeof(CoalitionDissolutionResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task DissolveCoalition(string coalitionId, [FromBody] CoalitionDissolutionRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid coalition dissolution 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, benefitDistribution, affectedAlliances) =
+ await _allianceService.DissolveCoalitionAsync(
+ coalitionId, kingdomId, request.DissolutionReason);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Coalition dissolution failed - insufficient authority or active operations",
+ Code = "DISSOLUTION_FAILED"
+ });
+ }
+
+ var response = new CoalitionDissolutionResponseDto
+ {
+ PlayerId = playerId,
+ CoalitionId = coalitionId,
+ Success = success,
+ BenefitDistribution = benefitDistribution,
+ AffectedAlliances = affectedAlliances,
+ DissolutionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Coalition dissolved successfully - Coalition ID: {CoalitionId}, Reason: {Reason}",
+ coalitionId, request.DissolutionReason);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error dissolving coalition {CoalitionId}", coalitionId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to dissolve coalition",
+ Code = "COALITION_DISSOLUTION_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Research Trees & Collective Benefits
+
+ ///
+ /// Processes alliance research progression across Military, Economic, and Technology branches
+ ///
+ /// Alliance conducting research
+ /// Research progression parameters
+ /// Research advancement result with new benefits unlocked
+ [HttpPost("{allianceId}/research/advance")]
+ [ProducesResponseType(typeof(ResearchAdvancementResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Insufficient resources or prerequisites
+ public async Task AdvanceResearch(int allianceId, [FromBody] ResearchAdvancementRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid research advancement 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, newResearchLevel, unlockedBenefits, contributionTracking) =
+ await _allianceService.ProcessAllianceResearchAsync(
+ allianceId, kingdomId, request.ResearchType, request.ResearchNode,
+ request.MemberContributions);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Research advancement failed - insufficient resources, missing prerequisites, or invalid node",
+ Code = "RESEARCH_FAILED"
+ });
+ }
+
+ var response = new ResearchAdvancementResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ ResearchType = request.ResearchType,
+ ResearchNode = request.ResearchNode,
+ NewResearchLevel = newResearchLevel,
+ UnlockedBenefits = unlockedBenefits,
+ ContributionTracking = contributionTracking,
+ AdvancementTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Research advanced for Alliance {AllianceId} - Type: {Type}, Node: {Node}, Level: {Level}",
+ allianceId, request.ResearchType, request.ResearchNode, newResearchLevel);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error advancing research for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to advance research",
+ Code = "RESEARCH_ADVANCEMENT_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Applies collective research benefits to all alliance members
+ ///
+ /// Alliance to apply benefits to
+ /// Applied benefits breakdown by member and research branch
+ [HttpPost("{allianceId}/research/apply-benefits")]
+ [ProducesResponseType(typeof(ResearchBenefitsResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ApplyResearchBenefits(int allianceId)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var appliedBenefits = await _allianceService.ApplyCollectiveResearchBenefitsAsync(
+ allianceId, kingdomId);
+
+ var response = new ResearchBenefitsResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ AppliedBenefits = appliedBenefits,
+ ApplicationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Research benefits applied for Alliance {AllianceId}", allianceId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error applying research benefits for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to apply research benefits",
+ Code = "BENEFITS_APPLICATION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Retrieves current research status across all branches
+ ///
+ /// Alliance to get research status for
+ /// Complete research status with progress and available nodes
+ [HttpGet("{allianceId}/research/status")]
+ [ProducesResponseType(typeof(ResearchStatusResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task GetResearchStatus(int allianceId)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var researchStatus = await _allianceService.GetResearchStatusAsync(allianceId, kingdomId);
+
+ var response = new ResearchStatusResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ ResearchStatus = researchStatus,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving research status for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve research status",
+ Code = "RESEARCH_STATUS_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Territory Management
+
+ ///
+ /// Claims territory for alliance with defense requirements
+ ///
+ /// Alliance claiming territory
+ /// Territory claiming parameters
+ /// Territory claiming result with defense setup
+ [HttpPost("{allianceId}/territory/claim")]
+ [ProducesResponseType(typeof(TerritoryClaimResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Territory occupied or insufficient power
+ public async Task ClaimTerritory(int allianceId, [FromBody] TerritoryClaimRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid territory claim 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, territoryId, defenseRequirements, strategicValue) =
+ await _allianceService.ClaimTerritoryAsync(
+ playerId, allianceId, kingdomId, request.TerritoryCoordinates,
+ request.TerritoryType, request.DefenseSetup);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Territory claim failed - area occupied, insufficient power, or defense requirements not met",
+ Code = "TERRITORY_CLAIM_FAILED"
+ });
+ }
+
+ var response = new TerritoryClaimResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ TerritoryId = territoryId,
+ DefenseRequirements = defenseRequirements,
+ StrategicValue = strategicValue,
+ ClaimTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Territory claimed successfully for Alliance {AllianceId} - Territory ID: {TerritoryId}",
+ allianceId, territoryId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error claiming territory for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to claim territory",
+ Code = "TERRITORY_CLAIM_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Constructs alliance buildings providing alliance-wide benefits
+ ///
+ /// Alliance constructing building
+ /// Building construction parameters
+ /// Building construction result with benefits
+ [HttpPost("{allianceId}/buildings/construct")]
+ [ProducesResponseType(typeof(BuildingConstructionResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)]
+ public async Task ConstructAllianceBuilding(int allianceId, [FromBody] BuildingConstructionRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid building construction 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, buildingId, constructionTime, allianceBenefits) =
+ await _allianceService.ConstructAllianceBuildingAsync(
+ playerId, allianceId, kingdomId, request.TerritoryId,
+ request.BuildingType, request.BuildingLevel, request.ResourceContributions);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Building construction failed - insufficient resources, invalid territory, or construction limit reached",
+ Code = "CONSTRUCTION_FAILED"
+ });
+ }
+
+ var response = new BuildingConstructionResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ BuildingId = buildingId,
+ ConstructionTime = constructionTime,
+ AllianceBenefits = allianceBenefits,
+ InitiationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Building construction initiated for Alliance {AllianceId} - Building: {BuildingType}, Territory: {TerritoryId}",
+ allianceId, request.BuildingType, request.TerritoryId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error constructing building for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to construct building",
+ Code = "BUILDING_CONSTRUCTION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Manages contested zones with speed reduction mechanics
+ ///
+ /// Contested zone management parameters
+ /// Contested zone management result
+ [HttpPost("territory/contested-zones/manage")]
+ [ProducesResponseType(typeof(ContestedZoneResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task ManageContestedZones([FromBody] ContestedZoneRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid contested zone request",
+ Code = "INVALID_REQUEST",
+ ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray())
+ });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var managementResults = await _allianceService.ManageContestedZonesAsync(
+ playerId, kingdomId, request.ZoneCoordinates, request.ManagementAction, request.ForestBarriers);
+
+ var response = new ContestedZoneResponseDto
+ {
+ PlayerId = playerId,
+ ManagementResults = managementResults,
+ ManagementTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Contested zones managed by Player {PlayerId} - Action: {Action}",
+ playerId, request.ManagementAction);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error managing contested zones for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to manage contested zones",
+ Code = "CONTESTED_ZONE_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Processes territory defense with banishment consequences
+ ///
+ /// Territory defense parameters
+ /// Territory defense result with consequences
+ [HttpPost("territory/process-defense")]
+ [ProducesResponseType(typeof(TerritoryDefenseResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task ProcessTerritoryDefense([FromBody] TerritoryDefenseRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid territory defense 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, defenseOutcome, banishmentConsequences, territoryChanges) =
+ await _allianceService.ProcessTerritoryDefenseAsync(
+ playerId, kingdomId, request.TerritoryId, request.AttackDetails, request.DefenseResponse);
+
+ var response = new TerritoryDefenseResponseDto
+ {
+ PlayerId = playerId,
+ TerritoryId = request.TerritoryId,
+ Success = success,
+ DefenseOutcome = defenseOutcome,
+ BanishmentConsequences = banishmentConsequences,
+ TerritoryChanges = territoryChanges,
+ DefenseTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Territory defense processed for Territory {TerritoryId} by Player {PlayerId} - Success: {Success}",
+ request.TerritoryId, playerId, success);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing territory defense for Territory {TerritoryId}", request.TerritoryId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process territory defense",
+ Code = "TERRITORY_DEFENSE_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Democratic Alliance Management
+
+ ///
+ /// Conducts democratic host selection for KvK events
+ ///
+ /// Alliance conducting election
+ /// Host selection parameters
+ /// Host selection result with democratic validation
+ [HttpPost("{allianceId}/elections/host-selection")]
+ [ProducesResponseType(typeof(HostSelectionResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ConductHostSelection(int allianceId, [FromBody] HostSelectionRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid host selection 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, selectedHost, votingResults, democraticValidation) =
+ await _allianceService.ConductDemocraticHostSelectionAsync(
+ playerId, allianceId, kingdomId, request.Candidates,
+ request.SelectionCriteria, request.VotingParameters);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Host selection failed - insufficient authority, invalid candidates, or voting fraud detected",
+ Code = "HOST_SELECTION_FAILED"
+ });
+ }
+
+ var response = new HostSelectionResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ SelectedHost = selectedHost,
+ VotingResults = votingResults,
+ DemocraticValidation = democraticValidation,
+ SelectionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Host selection completed for Alliance {AllianceId} - Selected Host: {HostId}",
+ allianceId, selectedHost);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error conducting host selection for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to conduct host selection",
+ Code = "HOST_SELECTION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Manages alliance leadership elections across 5-tier hierarchy
+ ///
+ /// Alliance conducting elections
+ /// Leadership election parameters
+ /// Election results with new leadership structure
+ [HttpPost("{allianceId}/elections/leadership")]
+ [ProducesResponseType(typeof(LeadershipElectionResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ConductLeadershipElection(int allianceId, [FromBody] LeadershipElectionRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid leadership election 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, newLeadership, electionResults, transitionPlan) =
+ await _allianceService.ProcessLeadershipElectionAsync(
+ playerId, allianceId, kingdomId, request.ElectionType,
+ request.Candidates, request.VotingPeriod);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Leadership election failed - insufficient authority or election fraud detected",
+ Code = "ELECTION_FAILED"
+ });
+ }
+
+ var response = new LeadershipElectionResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ NewLeadership = newLeadership,
+ ElectionResults = electionResults,
+ TransitionPlan = transitionPlan,
+ ElectionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Leadership election completed for Alliance {AllianceId} - Election Type: {Type}",
+ allianceId, request.ElectionType);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error conducting leadership election for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to conduct leadership election",
+ Code = "LEADERSHIP_ELECTION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Manages role-based permissions with authority validation
+ ///
+ /// Alliance to manage roles for
+ /// Role management parameters
+ /// Role management result with updated permissions
+ [HttpPost("{allianceId}/roles/manage")]
+ [ProducesResponseType(typeof(RoleManagementResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ManageRoles(int allianceId, [FromBody] RoleManagementRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid role management 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, updatedRoles, permissionChanges, authorityValidation) =
+ await _allianceService.ManageRolePermissionsAsync(
+ playerId, allianceId, kingdomId, request.RoleUpdates, request.PermissionChanges);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Role management failed - insufficient authority or invalid permission changes",
+ Code = "ROLE_MANAGEMENT_FAILED",
+ Details = new Dictionary { ["authorityValidation"] = authorityValidation }
+ });
+ }
+
+ var response = new RoleManagementResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ UpdatedRoles = updatedRoles,
+ PermissionChanges = permissionChanges,
+ AuthorityValidation = authorityValidation,
+ ManagementTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Roles managed for Alliance {AllianceId} by Player {PlayerId}",
+ allianceId, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error managing roles for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to manage roles",
+ Code = "ROLE_MANAGEMENT_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Conducts alliance-wide voting with fraud detection
+ ///
+ /// Alliance conducting vote
+ /// Alliance voting parameters
+ /// Voting results with fraud detection analysis
+ [HttpPost("{allianceId}/voting/conduct")]
+ [ProducesResponseType(typeof(AllianceVotingResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ConductAllianceVote(int allianceId, [FromBody] AllianceVotingRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid alliance voting 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, votingResults, fraudDetection, quorumMet) =
+ await _allianceService.ProcessAllianceVotingAsync(
+ playerId, allianceId, kingdomId, request.VotingTopic,
+ request.VotingOptions, request.VotingPeriod, request.QuorumRequirements);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Alliance voting failed - insufficient authority, voting fraud detected, or quorum not met",
+ Code = "VOTING_FAILED",
+ Details = new Dictionary
+ {
+ ["fraudDetection"] = fraudDetection,
+ ["quorumMet"] = quorumMet
+ }
+ });
+ }
+
+ var response = new AllianceVotingResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ VotingResults = votingResults,
+ FraudDetection = fraudDetection,
+ QuorumMet = quorumMet,
+ VotingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Alliance voting completed for Alliance {AllianceId} - Topic: {Topic}, Success: {Success}",
+ allianceId, request.VotingTopic, success);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error conducting alliance vote for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to conduct alliance vote",
+ Code = "ALLIANCE_VOTING_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Member Management
+
+ ///
+ /// Processes new member applications with screening and probation
+ ///
+ /// Alliance processing membership
+ /// Membership processing parameters
+ /// Membership processing result with screening details
+ [HttpPost("{allianceId}/members/process-application")]
+ [ProducesResponseType(typeof(MembershipProcessingResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task ProcessMembership(int allianceId, [FromBody] MembershipProcessingRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid membership 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 (success, membershipStatus, screeningResults, probationPeriod) =
+ await _allianceService.ProcessMembershipAsync(
+ playerId, allianceId, kingdomId, request.ApplicantId,
+ request.ApplicationType, request.ScreeningCriteria);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Membership processing failed - insufficient authority or applicant screening failed",
+ Code = "MEMBERSHIP_FAILED"
+ });
+ }
+
+ var response = new MembershipProcessingResponseDto
+ {
+ ProcessingPlayerId = playerId,
+ AllianceId = allianceId,
+ ApplicantId = request.ApplicantId,
+ Success = success,
+ MembershipStatus = membershipStatus,
+ ScreeningResults = screeningResults,
+ ProbationPeriod = probationPeriod,
+ ProcessingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Membership processed for Alliance {AllianceId} - Applicant: {ApplicantId}, Status: {Status}",
+ allianceId, request.ApplicantId, membershipStatus);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing membership for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process membership",
+ Code = "MEMBERSHIP_PROCESSING_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Monitors member activity with engagement metrics
+ ///
+ /// Alliance to monitor activity for
+ /// Activity monitoring timeframe (default: 7 days)
+ /// Member activity analysis with engagement recommendations
+ [HttpGet("{allianceId}/members/activity-analysis")]
+ [ProducesResponseType(typeof(MemberActivityResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task MonitorMemberActivity(int allianceId, [FromQuery] int timeframeDays = 7)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var activityAnalysis = await _allianceService.MonitorMemberActivityAsync(
+ playerId, allianceId, kingdomId, timeframeDays);
+
+ var response = new MemberActivityResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ TimeframeDays = timeframeDays,
+ ActivityAnalysis = activityAnalysis,
+ AnalysisTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error monitoring member activity for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to monitor member activity",
+ Code = "ACTIVITY_MONITORING_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Removes alliance member with benefit cleanup and territory implications
+ ///
+ /// Alliance to remove member from
+ /// Member removal parameters
+ /// Member removal result with cleanup details
+ [HttpPost("{allianceId}/members/remove")]
+ [ProducesResponseType(typeof(MemberRemovalResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task RemoveMember(int allianceId, [FromBody] MemberRemovalRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid member removal 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, removalDetails, benefitCleanup, territoryImplications) =
+ await _allianceService.RemoveMemberAsync(
+ playerId, allianceId, kingdomId, request.MemberId,
+ request.RemovalReason, request.BenefitHandling);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Member removal failed - insufficient authority or invalid removal reason",
+ Code = "REMOVAL_FAILED"
+ });
+ }
+
+ var response = new MemberRemovalResponseDto
+ {
+ RemovingPlayerId = playerId,
+ AllianceId = allianceId,
+ RemovedMemberId = request.MemberId,
+ Success = success,
+ RemovalDetails = removalDetails,
+ BenefitCleanup = benefitCleanup,
+ TerritoryImplications = territoryImplications,
+ RemovalTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Member removed from Alliance {AllianceId} - Removed Member: {MemberId}, Reason: {Reason}",
+ allianceId, request.MemberId, request.RemovalReason);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error removing member from Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to remove member",
+ Code = "MEMBER_REMOVAL_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Resource Sharing and Treasury
+
+ ///
+ /// Manages alliance treasury operations with audit trails
+ ///
+ /// Alliance treasury to manage
+ /// Treasury operation parameters
+ /// Treasury operation result with audit details
+ [HttpPost("{allianceId}/treasury/operate")]
+ [ProducesResponseType(typeof(TreasuryOperationResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task OperateTreasury(int allianceId, [FromBody] TreasuryOperationRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid treasury operation 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, operationResult, auditTrail, newTreasuryBalance) =
+ await _allianceService.ProcessTreasuryOperationAsync(
+ playerId, allianceId, kingdomId, request.OperationType,
+ request.ResourceAmounts, request.OperationReason);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Treasury operation failed - insufficient authority, invalid amounts, or operation restrictions",
+ Code = "TREASURY_OPERATION_FAILED"
+ });
+ }
+
+ var response = new TreasuryOperationResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ Success = success,
+ OperationType = request.OperationType,
+ OperationResult = operationResult,
+ AuditTrail = auditTrail,
+ NewTreasuryBalance = newTreasuryBalance,
+ OperationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Treasury operation completed for Alliance {AllianceId} - Type: {Type}, Player: {PlayerId}",
+ allianceId, request.OperationType, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing treasury operation for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process treasury operation",
+ Code = "TREASURY_OPERATION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Processes resource trading between alliance members
+ ///
+ /// Alliance facilitating trade
+ /// Resource trading parameters
+ /// Resource trading result with transaction details
+ [HttpPost("{allianceId}/resources/trade")]
+ [ProducesResponseType(typeof(ResourceTradingResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task TradeResources(int allianceId, [FromBody] ResourceTradingRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid resource trading 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, tradeDetails, transactionRecord, memberBenefits) =
+ await _allianceService.ProcessResourceTradingAsync(
+ playerId, allianceId, kingdomId, request.RecipientId,
+ request.ResourceOffers, request.TradeType);
+
+ if (!success)
+ {
+ return Forbid(new ErrorResponseDto
+ {
+ Message = "Resource trading failed - insufficient resources, invalid recipient, or trading restrictions",
+ Code = "TRADING_FAILED"
+ });
+ }
+
+ var response = new ResourceTradingResponseDto
+ {
+ TradingPlayerId = playerId,
+ AllianceId = allianceId,
+ RecipientId = request.RecipientId,
+ Success = success,
+ TradeDetails = tradeDetails,
+ TransactionRecord = transactionRecord,
+ MemberBenefits = memberBenefits,
+ TradingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Resource trading completed in Alliance {AllianceId} - From: {TraderId}, To: {RecipientId}",
+ allianceId, playerId, request.RecipientId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing resource trading for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process resource trading",
+ Code = "RESOURCE_TRADING_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Retrieves alliance treasury status and contribution tracking
+ ///
+ /// Alliance to get treasury status for
+ /// Treasury status with member contribution details
+ [HttpGet("{allianceId}/treasury/status")]
+ [ProducesResponseType(typeof(TreasuryStatusResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ public async Task GetTreasuryStatus(int allianceId)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var treasuryStatus = await _allianceService.GetTreasuryStatusAsync(allianceId, kingdomId);
+
+ var response = new TreasuryStatusResponseDto
+ {
+ PlayerId = playerId,
+ AllianceId = allianceId,
+ TreasuryStatus = treasuryStatus,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving treasury status for Alliance {AllianceId}", allianceId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve treasury status",
+ Code = "TREASURY_STATUS_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);
+ }
+
+ ///
+ /// Retrieves the alliance ID for the current authenticated player
+ ///
+ /// Player to get alliance for
+ /// Kingdom scope for security
+ /// Alliance ID (0 if not in alliance)
+ private async Task GetPlayerAllianceId(int playerId, int kingdomId)
+ {
+ try
+ {
+ var allianceInfo = await _allianceService.GetPlayerAllianceInfoAsync(playerId, kingdomId);
+ return allianceInfo.ContainsKey("allianceId") ? (int)allianceInfo["allianceId"] : 0;
+ }
+ catch
+ {
+ return 0; // Player not in alliance or error occurred
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Combat/CombatController.cs b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Combat/CombatController.cs
new file mode 100644
index 0000000..ec4ba7f
--- /dev/null
+++ b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Combat/CombatController.cs
@@ -0,0 +1,1083 @@
+/*
+ * File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Controllers\Combat\CombatController.cs
+ * Created: 2025-10-19
+ * Last Modified: 2025-10-19
+ * 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: Initial implementation exposing all CombatService functionality through RESTful endpoints
+ * with proper authentication, validation, and error handling.
+ */
+
+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();
+
+ var opportunities = await _combatService.CalculateInterceptionOpportunitiesAsync(
+ playerId, request.AttackingPlayerId, kingdomId, request.AttackRoute);
+
+ 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.InterceptionX, request.InterceptionY), 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
+ {
+ DefendingPlayerId = playerId,
+ AttackingPlayerId = request.AttackingPlayerId,
+ Success = success,
+ BattleSetup = battleSetup,
+ BattleStartTime = battleStartTime,
+ InterceptionPoint = new { X = request.InterceptionX, Y = request.InterceptionY }
+ };
+
+ _logger.LogInformation("Field interception executed successfully - Player {DefenderId} intercepted Player {AttackerId} at ({X}, {Y})",
+ playerId, request.AttackingPlayerId, request.InterceptionX, request.InterceptionY);
+
+ 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();
+
+ var (canIntercept, requirements, timing, restrictions) =
+ await _combatService.ValidateFieldInterceptionAsync(
+ playerId, request.AttackingPlayerId, kingdomId,
+ (request.InterceptionX, request.InterceptionY));
+
+ var response = new InterceptionValidationResponseDto
+ {
+ DefendingPlayerId = playerId,
+ AttackingPlayerId = request.AttackingPlayerId,
+ CanIntercept = canIntercept,
+ Requirements = requirements,
+ Timing = timing,
+ Restrictions = restrictions,
+ ValidationTime = DateTime.UtcNow
+ };
+
+ 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 optimalRoutes = await _combatService.CalculateOptimalInterceptionRoutesAsync(
+ playerId, request.AttackRoute, kingdomId);
+
+ var response = new OptimalRoutesResponseDto
+ {
+ DefendingPlayerId = playerId,
+ OptimalRoutes = optimalRoutes,
+ CalculationTime = DateTime.UtcNow
+ };
+
+ _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 (success, marchId, arrivalTime, marchSpeed) = await _combatService.InitiateMarchAsync(
+ playerId, kingdomId, (request.TargetX, request.TargetY),
+ request.TroopComposition, request.MarchType);
+
+ 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,
+ Success = success,
+ MarchId = marchId,
+ ArrivalTime = arrivalTime,
+ MarchSpeed = marchSpeed,
+ InitiationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("March initiated successfully for Player {PlayerId} - March ID: {MarchId}, Target: ({X}, {Y})",
+ playerId, marchId, request.TargetX, request.TargetY);
+
+ 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 (baseSpeed, finalSpeed, bonuses, diminishingReturns) =
+ await _combatService.CalculateMarchSpeedAsync(
+ playerId, kingdomId, request.TroopComposition, request.Distance);
+
+ var response = new MarchSpeedResponseDto
+ {
+ PlayerId = playerId,
+ BaseSpeed = baseSpeed,
+ FinalSpeed = finalSpeed,
+ SpeedBonuses = bonuses,
+ DiminishingReturns = diminishingReturns,
+ CalculationTime = 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, penaltiesApplied, troopsReturned) =
+ await _combatService.CancelMarchAsync(playerId, kingdomId, marchId);
+
+ 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 = marchId,
+ Success = success,
+ PenaltiesApplied = penaltiesApplied,
+ TroopsReturned = troopsReturned,
+ CancellationTime = DateTime.UtcNow
+ };
+
+ _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 (success, combatInitiated, battleDetails) =
+ await _combatService.ProcessMarchArrivalAsync(
+ playerId, kingdomId, request.MarchId, request.ArrivalValidation);
+
+ if (!success)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "March arrival processing failed - invalid march or arrival conditions",
+ Code = "ARRIVAL_FAILED"
+ });
+ }
+
+ var response = new MarchArrivalResponseDto
+ {
+ PlayerId = playerId,
+ MarchId = request.MarchId,
+ Success = success,
+ CombatInitiated = combatInitiated,
+ BattleDetails = battleDetails,
+ ArrivalTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("March arrival processed for Player {PlayerId} - Combat Initiated: {CombatInitiated}",
+ playerId, combatInitiated);
+
+ 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.ExecuteBattleAsync(
+ playerId, kingdomId, request.BattleSetup, request.CombatModifiers);
+
+ var response = new BattleExecutionResponseDto
+ {
+ BattleId = battleResults.ContainsKey("battleId") ? battleResults["battleId"].ToString() : null,
+ BattleResults = battleResults,
+ ExecutionTime = 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 (victoryProbability, casualtyEstimates, powerBalance, criticalFactors) =
+ await _combatService.CalculateBattlePredictionAsync(
+ playerId, kingdomId, request.AttackerForces, request.DefenderForces, request.BattleModifiers);
+
+ var response = new BattlePredictionResponseDto
+ {
+ PlayerId = playerId,
+ VictoryProbability = victoryProbability,
+ CasualtyEstimates = casualtyEstimates,
+ PowerBalance = powerBalance,
+ CriticalFactors = criticalFactors,
+ 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 (woundedTroops, killedTroops, hospitalCapacity, healingTimes) =
+ await _combatService.ProcessBattleCasualtiesAsync(
+ playerId, kingdomId, request.CasualtyData, request.HospitalModifiers);
+
+ var response = new CasualtyProcessingResponseDto
+ {
+ PlayerId = playerId,
+ WoundedTroops = woundedTroops,
+ KilledTroops = killedTroops,
+ HospitalCapacity = hospitalCapacity,
+ HealingTimes = healingTimes,
+ ProcessingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Battle casualties processed for Player {PlayerId} - Wounded: {Wounded}, Killed: {Killed}",
+ playerId, woundedTroops.Values.Sum(), killedTroops.Values.Sum());
+
+ 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 rewardsDistributed = await _combatService.DistributeBattleRewardsAsync(
+ playerId, kingdomId, request.BattleResults, request.ParticipationDetails);
+
+ var response = new RewardDistributionResponseDto
+ {
+ PlayerId = playerId,
+ RewardsDistributed = rewardsDistributed,
+ 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 (canParticipate, skillValidation, equipmentOptimal, recommendedSetup) =
+ await _combatService.ValidateDragonParticipationAsync(
+ playerId, kingdomId, request.DragonId, request.CombatType);
+
+ var response = new DragonValidationResponseDto
+ {
+ PlayerId = playerId,
+ DragonId = request.DragonId,
+ CanParticipate = canParticipate,
+ SkillValidation = skillValidation,
+ EquipmentOptimal = equipmentOptimal,
+ RecommendedSetup = recommendedSetup,
+ 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 (success, skillEffects, cooldownApplied, usesRemaining) =
+ await _combatService.ExecuteDragonSkillAsync(
+ playerId, kingdomId, request.DragonId, request.SkillType, request.TargetDetails);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Dragon skill execution failed - skill on cooldown, insufficient uses, or invalid target",
+ Code = "SKILL_UNAVAILABLE"
+ });
+ }
+
+ var response = new DragonSkillResponseDto
+ {
+ PlayerId = playerId,
+ DragonId = request.DragonId,
+ Success = success,
+ SkillEffects = skillEffects,
+ CooldownApplied = cooldownApplied,
+ UsesRemaining = usesRemaining,
+ ExecutionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Dragon skill executed for Player {PlayerId} - Dragon: {DragonId}, Skill: {SkillType}",
+ playerId, request.DragonId, request.SkillType);
+
+ 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 optimalEquipment = await _combatService.OptimizeDragonEquipmentAsync(
+ playerId, kingdomId, request.DragonId, request.CombatScenario, request.AvailableEquipment);
+
+ var response = new DragonEquipmentResponseDto
+ {
+ PlayerId = playerId,
+ DragonId = request.DragonId,
+ OptimalEquipment = optimalEquipment,
+ OptimizationTime = DateTime.UtcNow
+ };
+
+ 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,
+ EffectivenessAnalysis = effectivenessAnalysis,
+ 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,
+ GeneratedTime = DateTime.UtcNow
+ };
+
+ 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,
+ AnalysisType = analysisType,
+ Trends = trends,
+ GeneratedTime = DateTime.UtcNow
+ };
+
+ 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
+ {
+ CombatLogId = combatLogId,
+ ReplayData = replayData,
+ GeneratedTime = DateTime.UtcNow
+ };
+
+ _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
+ }
+}
\ No newline at end of file
diff --git a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Kingdom/KingdomController.cs b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Kingdom/KingdomController.cs
new file mode 100644
index 0000000..6ddee09
--- /dev/null
+++ b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Kingdom/KingdomController.cs
@@ -0,0 +1,903 @@
+/*
+ * File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Controllers\Kingdom\KingdomController.cs
+ * Created: 2025-10-19
+ * Last Modified: 2025-10-19
+ * Description: REST API controller for kingdom management operations including KvK events, democratic leadership,
+ * population management, and kingdom mergers.
+ * Last Edit Notes: Initial implementation using simplified response types to avoid DTO dependencies
+ */
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ShadowedRealms.Core.Interfaces.Services;
+using System.Security.Claims;
+using System.Text.Json;
+
+namespace ShadowedRealms.API.Controllers.Kingdom
+{
+ ///
+ /// REST API controller for comprehensive kingdom management operations
+ ///
+ [ApiController]
+ [Route("api/v1/kingdoms")]
+ [Authorize] // JWT authentication required for all kingdom operations
+ [Produces("application/json")]
+ public class KingdomController : ControllerBase
+ {
+ private readonly IKingdomService _kingdomService;
+ private readonly ILogger _logger;
+
+ public KingdomController(
+ IKingdomService kingdomService,
+ ILogger logger)
+ {
+ _kingdomService = kingdomService ?? throw new ArgumentNullException(nameof(kingdomService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ #region Kingdom Information
+
+ ///
+ /// Retrieves comprehensive kingdom information including population, leadership, and status
+ ///
+ /// Kingdom ID (optional - defaults to current player's kingdom)
+ /// Complete kingdom information
+ [HttpGet("{kingdomId?}")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(404)]
+ public async Task GetKingdomInfo(int? kingdomId = null)
+ {
+ try
+ {
+ var (playerId, playerKingdomId) = GetAuthenticatedPlayer();
+ var targetKingdomId = kingdomId ?? playerKingdomId;
+
+ var kingdomInfo = await _kingdomService.GetKingdomInfoAsync(targetKingdomId);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = targetKingdomId,
+ KingdomInfo = kingdomInfo,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Kingdom info retrieved for Kingdom {KingdomId} by Player {PlayerId}",
+ targetKingdomId, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving kingdom information for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to retrieve kingdom information", Code = "KINGDOM_INFO_ERROR" });
+ }
+ }
+
+ ///
+ /// Gets current kingdom population status with capacity and activity metrics
+ ///
+ /// Kingdom ID (optional - defaults to current player's kingdom)
+ /// Kingdom population analysis
+ [HttpGet("{kingdomId?}/population")]
+ [ProducesResponseType(typeof(object), 200)]
+ public async Task GetKingdomPopulation(int? kingdomId = null)
+ {
+ try
+ {
+ var (playerId, playerKingdomId) = GetAuthenticatedPlayer();
+ var targetKingdomId = kingdomId ?? playerKingdomId;
+
+ var populationData = await _kingdomService.MonitorKingdomPopulationAsync(targetKingdomId);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = targetKingdomId,
+ PopulationData = populationData,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving kingdom population for Kingdom {KingdomId}", kingdomId ?? GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to retrieve kingdom population", Code = "POPULATION_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region KvK Events Management
+
+ ///
+ /// Initiates Kingdom vs Kingdom event with multi-dimensional matchmaking
+ ///
+ /// KvK initiation parameters
+ /// KvK initiation result with matchmaking details
+ [HttpPost("kvk/initiate")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task InitiateKvKEvent([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid KvK initiation request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, kvkEventId, matchedKingdoms, eventSchedule) =
+ await _kingdomService.InitiateKvKEventAsync(
+ kingdomId,
+ requestDict?.ContainsKey("eventType") == true ? requestDict["eventType"].ToString() : "standard",
+ requestDict?.ContainsKey("matchmakingCriteria") == true ? (Dictionary)requestDict["matchmakingCriteria"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "KvK initiation failed - insufficient authority or invalid criteria", Code = "KVK_INITIATION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ KvKEventId = kvkEventId,
+ MatchedKingdoms = matchedKingdoms,
+ EventSchedule = eventSchedule,
+ InitiationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("KvK event initiated successfully for Kingdom {KingdomId} - Event ID: {EventId}",
+ kingdomId, kvkEventId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error initiating KvK event for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to initiate KvK event", Code = "KVK_INITIATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Coordinates KvK event operations with coalition support
+ ///
+ /// KvK event identifier
+ /// KvK coordination parameters
+ /// KvK coordination result
+ [HttpPost("kvk/{eventId}/coordinate")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(404)]
+ public async Task CoordinateKvKEvent(string eventId, [FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid KvK coordination request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var coordinationResult = await _kingdomService.CoordinateKvKEventAsync(
+ kingdomId, eventId,
+ requestDict?.ContainsKey("coalitionSupport") == true ? (Dictionary)requestDict["coalitionSupport"] : new(),
+ requestDict?.ContainsKey("strategyUpdates") == true ? (Dictionary)requestDict["strategyUpdates"] : new()
+ );
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ EventId = eventId,
+ CoordinationResult = coordinationResult,
+ CoordinationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("KvK coordination processed for Event {EventId} by Kingdom {KingdomId}",
+ eventId, kingdomId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error coordinating KvK event {EventId} for Kingdom {KingdomId}",
+ eventId, GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to coordinate KvK event", Code = "KVK_COORDINATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes KvK event conclusion with rankings and rewards
+ ///
+ /// KvK event identifier
+ /// KvK conclusion parameters
+ /// KvK conclusion result with rankings
+ [HttpPost("kvk/{eventId}/conclude")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ConcludeKvKEvent(string eventId, [FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid KvK conclusion request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, finalRankings, kingdomRewards) =
+ await _kingdomService.ProcessKvKConclusionAsync(
+ eventId,
+ requestDict?.ContainsKey("eventResults") == true ? (Dictionary)requestDict["eventResults"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "KvK conclusion failed - event not ready for conclusion or insufficient authority", Code = "KVK_CONCLUSION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ EventId = eventId,
+ Success = success,
+ FinalRankings = finalRankings,
+ KingdomRewards = kingdomRewards,
+ ConclusionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("KvK event concluded successfully - Event ID: {EventId}", eventId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error concluding KvK event {EventId}", eventId);
+ return StatusCode(500, new { Message = "Failed to conclude KvK event", Code = "KVK_CONCLUSION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes KvK season conclusion with seasonal rankings and rewards
+ ///
+ /// KvK season identifier
+ /// Season conclusion parameters
+ /// Season conclusion result
+ [HttpPost("kvk/season/{seasonId}/conclude")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ConcludeKvKSeason(string seasonId, [FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid season conclusion request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var seasonResults = await _kingdomService.ProcessKvKSeasonConclusionAsync(
+ seasonId,
+ requestDict?.ContainsKey("kingdomIds") == true ?
+ ((System.Text.Json.JsonElement)requestDict["kingdomIds"]).Deserialize>() ?? new() :
+ new() { kingdomId }
+ );
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ SeasonId = seasonId,
+ SeasonResults = seasonResults,
+ ConclusionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("KvK season concluded successfully - Season ID: {SeasonId}", seasonId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error concluding KvK season {SeasonId}", seasonId);
+ return StatusCode(500, new { Message = "Failed to conclude KvK season", Code = "SEASON_CONCLUSION_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Democratic Leadership
+
+ ///
+ /// Conducts democratic election for kingdom leadership positions
+ ///
+ /// Democratic election parameters
+ /// Election results with democratic validation
+ [HttpPost("elections/leadership")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ConductDemocraticElection([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid democratic election request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, electedLeaders, electionResults, votingTransparency) =
+ await _kingdomService.ConductDemocraticElectionAsync(
+ kingdomId,
+ requestDict?.ContainsKey("electionType") == true ? requestDict["electionType"].ToString() : "council",
+ requestDict?.ContainsKey("candidateIds") == true ?
+ ((System.Text.Json.JsonElement)requestDict["candidateIds"]).Deserialize>() ?? new() : new(),
+ requestDict?.ContainsKey("voterEligibility") == true ? (Dictionary)requestDict["voterEligibility"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Democratic election failed - insufficient authority or election fraud detected", Code = "ELECTION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ ElectedLeaders = electedLeaders,
+ ElectionResults = electionResults,
+ VotingTransparency = votingTransparency,
+ ElectionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Democratic election completed for Kingdom {KingdomId} - Election Type: {Type}",
+ kingdomId, requestDict?.ContainsKey("electionType") == true ? requestDict["electionType"].ToString() : "council");
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error conducting democratic election for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to conduct democratic election", Code = "DEMOCRATIC_ELECTION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes democratic host selection for KvK events
+ ///
+ /// Host selection parameters
+ /// Host selection result with democratic validation
+ [HttpPost("elections/kvk-host")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ProcessDemocraticHostSelection([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid host selection request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, selectedHostPlayerId, hostAuthorities, selectionValidation) =
+ await _kingdomService.ProcessDemocraticHostSelectionAsync(
+ kingdomId,
+ requestDict?.ContainsKey("kvkEventId") == true ? requestDict["kvkEventId"].ToString() : "",
+ requestDict?.ContainsKey("allianceVotes") == true ? (Dictionary>)requestDict["allianceVotes"] : new(),
+ requestDict?.ContainsKey("selectionCriteria") == true ? (Dictionary)requestDict["selectionCriteria"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Host selection failed - insufficient authority or selection fraud detected", Code = "HOST_SELECTION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ SelectedHostPlayerId = selectedHostPlayerId,
+ HostAuthorities = hostAuthorities,
+ SelectionValidation = selectionValidation,
+ SelectionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Host selection completed for Kingdom {KingdomId} - Selected Host: {HostId}",
+ kingdomId, selectedHostPlayerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing host selection for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to process host selection", Code = "HOST_SELECTION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes democratic tax distribution with transparent allocation
+ ///
+ /// Tax distribution parameters
+ /// Tax distribution result with allocation transparency
+ [HttpPost("tax-distribution")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ProcessDemocraticTaxDistribution([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid tax distribution request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, distributionPlan, allianceAllocations, distributionAudit) =
+ await _kingdomService.ProcessDemocraticTaxDistributionAsync(
+ kingdomId,
+ requestDict?.ContainsKey("taxCollectionData") == true ? (Dictionary)requestDict["taxCollectionData"] : new(),
+ requestDict?.ContainsKey("distributionCriteria") == true ? (Dictionary)requestDict["distributionCriteria"] : new(),
+ requestDict?.ContainsKey("councilApproval") == true ? (Dictionary)requestDict["councilApproval"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Tax distribution failed - insufficient council approval or invalid criteria", Code = "TAX_DISTRIBUTION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ DistributionPlan = distributionPlan,
+ AllianceAllocations = allianceAllocations,
+ DistributionAudit = distributionAudit,
+ DistributionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Democratic tax distribution processed for Kingdom {KingdomId}", kingdomId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing tax distribution for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to process tax distribution", Code = "TAX_DISTRIBUTION_ERROR" });
+ }
+ }
+
+ ///
+ /// Manages royal council operations with democratic decision-making
+ ///
+ /// Royal council parameters
+ /// Council operation result with voting records
+ [HttpPost("royal-council/operate")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task OperateRoyalCouncil([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid royal council request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, actionResult, councilVotes, governanceTransparency) =
+ await _kingdomService.OperateRoyalCouncilAsync(
+ kingdomId,
+ requestDict?.ContainsKey("councilAction") == true ? requestDict["councilAction"].ToString() : "",
+ requestDict?.ContainsKey("councilMembers") == true ?
+ ((System.Text.Json.JsonElement)requestDict["councilMembers"]).Deserialize>() ?? new() : new(),
+ requestDict?.ContainsKey("actionDetails") == true ? (Dictionary)requestDict["actionDetails"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Royal council operation failed - insufficient authority or council rejection", Code = "COUNCIL_OPERATION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ ActionResult = actionResult,
+ CouncilVotes = councilVotes,
+ GovernanceTransparency = governanceTransparency,
+ OperationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Royal council operation processed for Kingdom {KingdomId} - Action: {Action}",
+ kingdomId, requestDict?.ContainsKey("councilAction") == true ? requestDict["councilAction"].ToString() : "unknown");
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error operating royal council for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to operate royal council", Code = "ROYAL_COUNCIL_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Population Management
+
+ ///
+ /// Monitors kingdom population with scaling recommendations
+ ///
+ /// Kingdom ID (optional - defaults to current player's kingdom)
+ /// Population monitoring analysis
+ [HttpGet("{kingdomId?}/population/monitor")]
+ [ProducesResponseType(typeof(object), 200)]
+ public async Task MonitorKingdomPopulation(int? kingdomId = null)
+ {
+ try
+ {
+ var (playerId, playerKingdomId) = GetAuthenticatedPlayer();
+ var targetKingdomId = kingdomId ?? playerKingdomId;
+
+ var monitoringResult = await _kingdomService.MonitorKingdomPopulationAsync(targetKingdomId);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = targetKingdomId,
+ MonitoringResult = monitoringResult,
+ MonitoringTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error monitoring kingdom population for Kingdom {KingdomId}",
+ kingdomId ?? GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to monitor kingdom population", Code = "POPULATION_MONITORING_ERROR" });
+ }
+ }
+
+ ///
+ /// Creates new kingdom when population capacity is exceeded
+ ///
+ /// Kingdom creation parameters
+ /// Kingdom creation result
+ [HttpPost("create")]
+ [ProducesResponseType(typeof(object), 201)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task CreateKingdom([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid kingdom creation request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, newKingdomId, migrationIncentives) =
+ await _kingdomService.CreateKingdomAsync(
+ requestDict?.ContainsKey("kingdomName") == true ? requestDict["kingdomName"].ToString() : "New Kingdom",
+ requestDict?.ContainsKey("initialSettings") == true ? (Dictionary)requestDict["initialSettings"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Kingdom creation failed - insufficient authority or invalid settings", Code = "KINGDOM_CREATION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ RequestingKingdomId = kingdomId,
+ Success = success,
+ NewKingdomId = newKingdomId,
+ MigrationIncentives = migrationIncentives,
+ CreationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Kingdom created successfully - New Kingdom ID: {NewKingdomId}, Created by Player {PlayerId}",
+ newKingdomId, playerId);
+
+ return CreatedAtAction(nameof(GetKingdomInfo), new { kingdomId = newKingdomId }, response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error creating kingdom for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to create kingdom", Code = "KINGDOM_CREATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Manages kingdom migration with incentive programs
+ ///
+ /// Migration management parameters
+ /// Migration management result
+ [HttpPost("migration/manage")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ManageKingdomMigration([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid migration management request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var migrationResult = await _kingdomService.ManageMigrationIncentivesAsync(
+ kingdomId,
+ requestDict?.ContainsKey("targetKingdomIds") == true ?
+ ((System.Text.Json.JsonElement)requestDict["targetKingdomIds"]).Deserialize>() ?? new() : new(),
+ requestDict?.ContainsKey("incentivePrograms") == true ? (Dictionary)requestDict["incentivePrograms"] : new()
+ );
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ MigrationResult = migrationResult,
+ ManagementTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Kingdom migration managed for Kingdom {KingdomId} by Player {PlayerId}",
+ kingdomId, playerId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error managing kingdom migration for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to manage kingdom migration", Code = "MIGRATION_MANAGEMENT_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Kingdom Mergers
+
+ ///
+ /// Initiates voluntary kingdom merger process with democratic approval
+ ///
+ /// Kingdom merger parameters
+ /// Kingdom merger initiation result
+ [HttpPost("merger/initiate")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task InitiateKingdomMerger([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid kingdom merger request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, mergerProposalId, compatibilityAnalysis, democraticRequirements) =
+ await _kingdomService.InitiateKingdomMergerAsync(
+ kingdomId,
+ requestDict?.ContainsKey("targetKingdomIds") == true ?
+ ((System.Text.Json.JsonElement)requestDict["targetKingdomIds"]).Deserialize>() ?? new() : new(),
+ requestDict?.ContainsKey("mergerReasoning") == true ? requestDict["mergerReasoning"].ToString() : "",
+ requestDict?.ContainsKey("proposedTerms") == true ? (Dictionary)requestDict["proposedTerms"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Kingdom merger initiation failed - insufficient authority or incompatible kingdoms", Code = "MERGER_INITIATION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ MergerProposalId = mergerProposalId,
+ CompatibilityAnalysis = compatibilityAnalysis,
+ DemocraticRequirements = democraticRequirements,
+ InitiationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Kingdom merger initiated for Kingdom {KingdomId} - Proposal ID: {ProposalId}",
+ kingdomId, mergerProposalId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error initiating kingdom merger for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to initiate kingdom merger", Code = "MERGER_INITIATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes democratic approval for kingdom merger
+ ///
+ /// Merger proposal identifier
+ /// Merger approval parameters
+ /// Merger approval result with democratic validation
+ [HttpPost("merger/{mergerProposalId}/approve")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ProcessMergerApproval(string mergerProposalId, [FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid merger approval request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, approvalStatus, votingResults, nextSteps) =
+ await _kingdomService.ProcessMergerApprovalAsync(
+ mergerProposalId, kingdomId,
+ requestDict?.ContainsKey("kingdomVotes") == true ? (Dictionary)requestDict["kingdomVotes"] : new(),
+ requestDict?.ContainsKey("voterTurnout") == true ? (Dictionary)requestDict["voterTurnout"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Merger approval failed - insufficient votes or approval fraud detected", Code = "MERGER_APPROVAL_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ MergerProposalId = mergerProposalId,
+ Success = success,
+ ApprovalStatus = approvalStatus,
+ VotingResults = votingResults,
+ NextSteps = nextSteps,
+ ApprovalTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Merger approval processed for Proposal {ProposalId} - Status: {Status}",
+ mergerProposalId, approvalStatus);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing merger approval for Proposal {ProposalId}", mergerProposalId);
+ return StatusCode(500, new { Message = "Failed to process merger approval", Code = "MERGER_APPROVAL_ERROR" });
+ }
+ }
+
+ ///
+ /// Executes approved kingdom merger with integration planning
+ ///
+ /// Merger proposal identifier
+ /// Merger execution parameters
+ /// Merger execution result
+ [HttpPost("merger/{mergerProposalId}/execute")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task ExecuteKingdomMerger(string mergerProposalId, [FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid merger execution request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var (success, resultingKingdomId, integrationPlan, preservedBenefits) =
+ await _kingdomService.ExecuteKingdomMergerAsync(
+ mergerProposalId,
+ requestDict?.ContainsKey("integrationStrategy") == true ? (Dictionary)requestDict["integrationStrategy"] : new(),
+ requestDict?.ContainsKey("leadershipIntegration") == true ? (Dictionary)requestDict["leadershipIntegration"] : new()
+ );
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Kingdom merger execution failed - merger not approved or execution error", Code = "MERGER_EXECUTION_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ OriginKingdomId = kingdomId,
+ MergerProposalId = mergerProposalId,
+ Success = success,
+ ResultingKingdomId = resultingKingdomId,
+ IntegrationPlan = integrationPlan,
+ PreservedBenefits = preservedBenefits,
+ ExecutionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Kingdom merger executed successfully - Proposal {ProposalId}, Resulting Kingdom: {ResultingKingdomId}",
+ mergerProposalId, resultingKingdomId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error executing kingdom merger for Proposal {ProposalId}", mergerProposalId);
+ return StatusCode(500, new { Message = "Failed to execute kingdom merger", Code = "MERGER_EXECUTION_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
+ }
+}
\ No newline at end of file
diff --git a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Player/PlayerController.cs b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Player/PlayerController.cs
new file mode 100644
index 0000000..3fc744f
--- /dev/null
+++ b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Player/PlayerController.cs
@@ -0,0 +1,1029 @@
+/*
+ * File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Controllers\Player\PlayerController.cs
+ * Created: 2025-10-19
+ * Last Modified: 2025-10-19
+ * Description: Comprehensive REST API controller for player management operations.
+ * Exposes all PlayerService functionality through RESTful endpoints with
+ * proper authentication, validation, and error handling.
+ * Last Edit Notes: Initial implementation with complete CRUD operations, castle management,
+ * VIP progression, teleportation, resource management, and social integration.
+ */
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ShadowedRealms.Core.Interfaces.Services;
+using ShadowedRealms.Shared.DTOs.Player;
+using ShadowedRealms.Shared.DTOs.Common;
+using System.Security.Claims;
+
+namespace ShadowedRealms.API.Controllers.Player
+{
+ ///
+ /// REST API controller for comprehensive player management operations
+ ///
+ [ApiController]
+ [Route("api/v1/players")]
+ [Authorize] // JWT authentication required for all endpoints
+ [Produces("application/json")]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 401)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 403)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 500)]
+ public class PlayerController : ControllerBase
+ {
+ private readonly IPlayerService _playerService;
+ private readonly ILogger _logger;
+
+ public PlayerController(
+ IPlayerService playerService,
+ ILogger logger)
+ {
+ _playerService = playerService ?? throw new ArgumentNullException(nameof(playerService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ #region Player Information and Profile Management
+
+ ///
+ /// Retrieves current player profile with comprehensive statistics
+ ///
+ /// Complete player profile including castle, resources, VIP status, and alliance
+ [HttpGet("profile")]
+ [ProducesResponseType(typeof(PlayerProfileResponseDto), 200)]
+ public async Task GetPlayerProfile()
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var playerInfo = await _playerService.GetPlayerInfoAsync(playerId, kingdomId);
+
+ var response = new PlayerProfileResponseDto
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ PlayerInfo = playerInfo,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Player profile retrieved successfully for Player {PlayerId} in Kingdom {KingdomId}",
+ playerId, kingdomId);
+
+ return Ok(response);
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ _logger.LogWarning("Unauthorized access attempt: {Message}", ex.Message);
+ return Forbid();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving player profile");
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve player profile",
+ Code = "PROFILE_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Updates player profile settings and preferences
+ ///
+ /// Profile update settings
+ /// Updated profile information
+ [HttpPut("profile")]
+ [ProducesResponseType(typeof(PlayerProfileResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task UpdatePlayerProfile([FromBody] UpdatePlayerProfileRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid profile update data",
+ Code = "INVALID_INPUT",
+ ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray())
+ });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ // Validate profile update settings
+ var validationResult = await _playerService.ValidatePlayerActionsAsync(
+ playerId, kingdomId, "profile_update", request.ToValidationDictionary());
+
+ if (!validationResult.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Profile update validation failed",
+ Code = "VALIDATION_FAILED",
+ Details = validationResult.ValidationErrors
+ });
+ }
+
+ // Process profile updates
+ var updateResult = await _playerService.UpdatePlayerSettingsAsync(
+ playerId, kingdomId, request.Settings);
+
+ var response = new PlayerProfileResponseDto
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ PlayerInfo = updateResult,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Player profile updated successfully for Player {PlayerId}", playerId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error updating player profile");
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to update player profile",
+ Code = "UPDATE_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Retrieves current player rankings and leaderboard positions
+ ///
+ /// Ranking category (power, castle_level, kills, etc.)
+ /// Player rankings and position information
+ [HttpGet("rankings")]
+ [ProducesResponseType(typeof(PlayerRankingsResponseDto), 200)]
+ public async Task GetPlayerRankings([FromQuery] string category = "power")
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var rankings = await _playerService.GetPlayerRankingsAsync(playerId, kingdomId, category);
+
+ var response = new PlayerRankingsResponseDto
+ {
+ PlayerId = playerId,
+ Category = category,
+ Rankings = rankings,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving player rankings for category {Category}", category);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve rankings",
+ Code = "RANKINGS_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Castle Management and Upgrades
+
+ ///
+ /// Retrieves current castle information including level, upgrades in progress, and requirements
+ ///
+ /// Complete castle status and upgrade information
+ [HttpGet("castle")]
+ [ProducesResponseType(typeof(CastleInfoResponseDto), 200)]
+ public async Task GetCastleInfo()
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var castleInfo = await _playerService.GetCastleInfoAsync(playerId, kingdomId);
+
+ var response = new CastleInfoResponseDto
+ {
+ PlayerId = playerId,
+ CastleInfo = castleInfo,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving castle information for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve castle information",
+ Code = "CASTLE_INFO_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Initiates castle upgrade process with resource validation and VIP bonuses
+ ///
+ /// Castle upgrade parameters
+ /// Upgrade result with new castle level and benefits granted
+ [HttpPost("castle/upgrade")]
+ [ProducesResponseType(typeof(CastleUpgradeResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Conflict - upgrade already in progress
+ public async Task UpgradeCastle([FromBody] CastleUpgradeRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid castle upgrade 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, newCastleLevel, benefitsGranted) = await _playerService.UpgradeCastleAsync(
+ playerId, kingdomId, request.UseSpeedups);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Castle upgrade failed - insufficient resources or upgrade already in progress",
+ Code = "UPGRADE_FAILED"
+ });
+ }
+
+ var response = new CastleUpgradeResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ NewCastleLevel = newCastleLevel,
+ BenefitsGranted = benefitsGranted,
+ UpgradeTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Castle upgraded successfully for Player {PlayerId} to level {NewLevel}",
+ playerId, newCastleLevel);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error upgrading castle for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to upgrade castle",
+ Code = "UPGRADE_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region VIP Management and Progression
+
+ ///
+ /// Retrieves current VIP status including tier, benefits, and progression
+ ///
+ /// Complete VIP information and secret tier status
+ [HttpGet("vip")]
+ [ProducesResponseType(typeof(VipStatusResponseDto), 200)]
+ public async Task GetVipStatus()
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var vipStatus = await _playerService.GetVipStatusAsync(playerId, kingdomId);
+
+ var response = new VipStatusResponseDto
+ {
+ PlayerId = playerId,
+ VipStatus = vipStatus,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving VIP status for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve VIP status",
+ Code = "VIP_STATUS_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Processes VIP advancement based on purchases and milestones
+ ///
+ /// VIP advancement parameters
+ /// VIP advancement result with new tier and benefits
+ [HttpPost("vip/advance")]
+ [ProducesResponseType(typeof(VipAdvancementResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task AdvanceVip([FromBody] VipAdvancementRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid VIP advancement 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, newVipTier, benefitsGranted, secretTierUnlocked) =
+ await _playerService.ProcessVipAdvancementAsync(
+ playerId, kingdomId, request.PurchaseAmount, request.PurchaseType);
+
+ if (!success)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "VIP advancement failed - invalid purchase or tier requirements not met",
+ Code = "ADVANCEMENT_FAILED"
+ });
+ }
+
+ var response = new VipAdvancementResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ NewVipTier = newVipTier,
+ BenefitsGranted = benefitsGranted,
+ SecretTierUnlocked = secretTierUnlocked,
+ AdvancementTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("VIP advancement processed for Player {PlayerId} to tier {NewTier}",
+ playerId, newVipTier);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing VIP advancement for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process VIP advancement",
+ Code = "VIP_ADVANCEMENT_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Teleportation and Movement
+
+ ///
+ /// Executes player teleportation with distance calculations and cost validation
+ ///
+ /// Teleportation target coordinates and parameters
+ /// Teleportation result with new position and costs
+ [HttpPost("teleport")]
+ [ProducesResponseType(typeof(TeleportationResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)] // Conflict - cooldown or proximity block
+ public async Task ExecuteTeleportation([FromBody] TeleportationRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid teleportation 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, newPosition, costsApplied, cooldownApplied, proximityBlocked) =
+ await _playerService.ExecuteTeleportationAsync(
+ playerId, kingdomId, request.TargetX, request.TargetY, request.TeleportType);
+
+ if (!success)
+ {
+ var errorMessage = proximityBlocked ? "Teleportation blocked due to proximity restrictions" :
+ cooldownApplied > TimeSpan.Zero ? "Teleportation on cooldown" :
+ "Teleportation failed - insufficient resources or invalid target";
+
+ return Conflict(new ErrorResponseDto
+ {
+ Message = errorMessage,
+ Code = "TELEPORTATION_FAILED",
+ Details = new Dictionary
+ {
+ ["proximityBlocked"] = proximityBlocked,
+ ["cooldownRemaining"] = cooldownApplied.TotalSeconds
+ }
+ });
+ }
+
+ var response = new TeleportationResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ NewPosition = newPosition,
+ CostsApplied = costsApplied,
+ CooldownApplied = cooldownApplied,
+ TeleportationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Teleportation executed successfully for Player {PlayerId} to ({X}, {Y})",
+ playerId, request.TargetX, request.TargetY);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error executing teleportation for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to execute teleportation",
+ Code = "TELEPORTATION_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Resource Management
+
+ ///
+ /// Retrieves current player resources including production rates and capacity
+ ///
+ /// Complete resource status and production information
+ [HttpGet("resources")]
+ [ProducesResponseType(typeof(ResourceStatusResponseDto), 200)]
+ public async Task GetResourceStatus()
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var resources = await _playerService.GetResourceStatusAsync(playerId, kingdomId);
+
+ var response = new ResourceStatusResponseDto
+ {
+ PlayerId = playerId,
+ Resources = resources,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving resource status for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve resource status",
+ Code = "RESOURCE_STATUS_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Collects accumulated resources from production buildings
+ ///
+ /// Resource collection result with amounts collected
+ [HttpPost("resources/collect")]
+ [ProducesResponseType(typeof(ResourceCollectionResponseDto), 200)]
+ public async Task CollectResources()
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var (success, resourcesCollected, bonusesApplied) =
+ await _playerService.CollectResourcesAsync(playerId, kingdomId);
+
+ var response = new ResourceCollectionResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ ResourcesCollected = resourcesCollected,
+ BonusesApplied = bonusesApplied,
+ CollectionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Resources collected successfully for Player {PlayerId}", playerId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error collecting resources for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to collect resources",
+ Code = "RESOURCE_COLLECTION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Spends resources for various game actions with validation
+ ///
+ /// Resource spending parameters
+ /// Resource spending result with validation
+ [HttpPost("resources/spend")]
+ [ProducesResponseType(typeof(ResourceSpendingResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task SpendResources([FromBody] ResourceSpendingRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid resource spending 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, remainingResources, spendingValidated) =
+ await _playerService.SpendResourcesAsync(
+ playerId, kingdomId, request.ResourceCosts, request.SpendingReason);
+
+ if (!success)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Resource spending failed - insufficient resources or invalid spending reason",
+ Code = "SPENDING_FAILED"
+ });
+ }
+
+ var response = new ResourceSpendingResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ RemainingResources = remainingResources,
+ SpendingValidated = spendingValidated,
+ SpendingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Resources spent successfully for Player {PlayerId} - Reason: {Reason}",
+ playerId, request.SpendingReason);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error spending resources for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to spend resources",
+ Code = "RESOURCE_SPENDING_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Combat Integration
+
+ ///
+ /// Prepares player for combat with troop validation and march setup
+ ///
+ /// Combat preparation parameters
+ /// Combat preparation result with march details
+ [HttpPost("combat/prepare")]
+ [ProducesResponseType(typeof(CombatPreparationResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task PrepareCombat([FromBody] CombatPreparationRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid combat preparation 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, marchDetails, validationResults) =
+ await _playerService.PrepareCombatAsync(
+ playerId, kingdomId, request.TroopComposition, request.TargetType);
+
+ if (!success)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Combat preparation failed - invalid troop composition or target",
+ Code = "PREPARATION_FAILED",
+ Details = validationResults
+ });
+ }
+
+ var response = new CombatPreparationResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ MarchDetails = marchDetails,
+ ValidationResults = validationResults,
+ PreparationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Combat prepared successfully for Player {PlayerId} - Target: {TargetType}",
+ playerId, request.TargetType);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error preparing combat for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to prepare combat",
+ Code = "COMBAT_PREPARATION_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Processes combat results and updates player statistics
+ ///
+ /// Combat result processing parameters
+ /// Combat result processing outcome
+ [HttpPost("combat/process-results")]
+ [ProducesResponseType(typeof(CombatResultsResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task ProcessCombatResults([FromBody] CombatResultsRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid combat results request",
+ Code = "INVALID_REQUEST",
+ ValidationErrors = ModelState.ToDictionary(x => x.Key, x => x.Value?.Errors.Select(e => e.ErrorMessage).ToArray())
+ });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var playerUpdates = await _playerService.ProcessCombatResultsAsync(
+ playerId, kingdomId, request.CombatResult);
+
+ var response = new CombatResultsResponseDto
+ {
+ PlayerId = playerId,
+ PlayerUpdates = playerUpdates,
+ ProcessingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Combat results processed successfully for Player {PlayerId}", playerId);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing combat results for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process combat results",
+ Code = "COMBAT_RESULTS_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Alliance Integration
+
+ ///
+ /// Processes alliance join request or invitation
+ ///
+ /// Alliance join parameters
+ /// Alliance join result with benefits and privileges
+ [HttpPost("alliance/join")]
+ [ProducesResponseType(typeof(AllianceJoinResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 409)]
+ public async Task JoinAlliance([FromBody] AllianceJoinRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid alliance join 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, allianceDetails, newPrivileges) =
+ await _playerService.ProcessAllianceJoinAsync(
+ playerId, kingdomId, request.AllianceId, request.IsInvitation);
+
+ if (!success)
+ {
+ return Conflict(new ErrorResponseDto
+ {
+ Message = "Alliance join failed - player already in alliance, alliance full, or invitation invalid",
+ Code = "JOIN_FAILED"
+ });
+ }
+
+ var response = new AllianceJoinResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ AllianceDetails = allianceDetails,
+ NewPrivileges = newPrivileges,
+ JoinTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Player {PlayerId} successfully joined alliance {AllianceId}",
+ playerId, request.AllianceId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing alliance join for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to join alliance",
+ Code = "ALLIANCE_JOIN_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Processes alliance leave with benefit adjustments
+ ///
+ /// Alliance leave parameters
+ /// Alliance leave result with benefit adjustments
+ [HttpPost("alliance/leave")]
+ [ProducesResponseType(typeof(AllianceLeaveResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task LeaveAlliance([FromBody] AllianceLeaveRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid alliance leave 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, benefitsLost, territoryEviction) =
+ await _playerService.ProcessAllianceLeaveAsync(
+ playerId, kingdomId, request.Reason);
+
+ if (!success)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Alliance leave failed - player not in alliance or invalid reason",
+ Code = "LEAVE_FAILED"
+ });
+ }
+
+ var response = new AllianceLeaveResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ BenefitsLost = benefitsLost,
+ TerritoryEviction = territoryEviction,
+ LeaveTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Player {PlayerId} left alliance - Reason: {Reason}",
+ playerId, request.Reason);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing alliance leave for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to leave alliance",
+ Code = "ALLIANCE_LEAVE_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Player Progression and Achievements
+
+ ///
+ /// Processes experience gain and level progression
+ ///
+ /// Experience processing parameters
+ /// Experience processing result with level progression
+ [HttpPost("experience/process")]
+ [ProducesResponseType(typeof(ExperienceProcessingResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task ProcessExperience([FromBody] ExperienceProcessingRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid experience 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 (success, newLevel, achievementsUnlocked, powerIncrease) =
+ await _playerService.ProcessExperienceAsync(
+ playerId, kingdomId, request.ExperienceGained, request.ExperienceSource);
+
+ if (!success)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Experience processing failed - invalid experience source or amount",
+ Code = "EXPERIENCE_FAILED"
+ });
+ }
+
+ var response = new ExperienceProcessingResponseDto
+ {
+ PlayerId = playerId,
+ Success = success,
+ NewLevel = newLevel,
+ AchievementsUnlocked = achievementsUnlocked,
+ PowerIncrease = powerIncrease,
+ ProcessingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Experience processed for Player {PlayerId} - Gained: {Experience} from {Source}",
+ playerId, request.ExperienceGained, request.ExperienceSource);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing experience for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to process experience",
+ Code = "EXPERIENCE_ERROR"
+ });
+ }
+ }
+
+ ///
+ /// Retrieves achievement progress and unlocked achievements
+ ///
+ /// Achievement status and progress information
+ [HttpGet("achievements")]
+ [ProducesResponseType(typeof(AchievementsResponseDto), 200)]
+ public async Task GetAchievements()
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var achievements = await _playerService.GetAchievementProgressAsync(playerId, kingdomId);
+
+ var response = new AchievementsResponseDto
+ {
+ PlayerId = playerId,
+ Achievements = achievements,
+ LastUpdated = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error retrieving achievements for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to retrieve achievements",
+ Code = "ACHIEVEMENTS_ERROR"
+ });
+ }
+ }
+
+ #endregion
+
+ #region Anti-Exploitation and Validation
+
+ ///
+ /// Validates player actions for potential exploitation or cheating
+ ///
+ /// Action validation parameters
+ /// Validation result and risk assessment
+ [HttpPost("validate-action")]
+ [ProducesResponseType(typeof(ActionValidationResponseDto), 200)]
+ [ProducesResponseType(typeof(ErrorResponseDto), 400)]
+ public async Task ValidateAction([FromBody] ActionValidationRequestDto request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new ErrorResponseDto
+ {
+ Message = "Invalid action 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, riskScore, validationNotes) =
+ await _playerService.ValidatePlayerActionsAsync(
+ playerId, kingdomId, request.ActionType, request.ActionDetails);
+
+ var response = new ActionValidationResponseDto
+ {
+ PlayerId = playerId,
+ IsValid = isValid,
+ RiskScore = riskScore,
+ ValidationNotes = validationNotes,
+ ValidationTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating action for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new ErrorResponseDto
+ {
+ Message = "Failed to validate action",
+ Code = "VALIDATION_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
+ }
+}
\ No newline at end of file
diff --git a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Purchase/PurchaseController.cs b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Purchase/PurchaseController.cs
new file mode 100644
index 0000000..0e64f09
--- /dev/null
+++ b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/Controllers/Purchase/PurchaseController.cs
@@ -0,0 +1,1028 @@
+/*
+ * File: D:\shadowed-realms-mobile\ShadowedRealmsMobile\src\server\ShadowedRealms.API\Controllers\Purchase\PurchaseController.cs
+ * Created: 2025-10-19
+ * Last Modified: 2025-10-19
+ * Description: REST API controller for purchase operations including ethical monetization, anti-pay-to-win monitoring,
+ * VIP progression, and skill-based alternatives with comprehensive player protection systems.
+ * Last Edit Notes: Initial implementation using simplified response types to avoid DTO dependencies
+ */
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using ShadowedRealms.Core.Interfaces.Services;
+using System.Security.Claims;
+using System.Text.Json;
+
+namespace ShadowedRealms.API.Controllers.Purchase
+{
+ ///
+ /// REST API controller for ethical monetization and purchase operations
+ ///
+ [ApiController]
+ [Route("api/v1/purchases")]
+ [Authorize] // JWT authentication required for all purchase operations
+ [Produces("application/json")]
+ public class PurchaseController : ControllerBase
+ {
+ private readonly IPurchaseService _purchaseService;
+ private readonly ILogger _logger;
+
+ public PurchaseController(
+ IPurchaseService purchaseService,
+ ILogger logger)
+ {
+ _purchaseService = purchaseService ?? throw new ArgumentNullException(nameof(purchaseService));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ #region Purchase Processing
+
+ ///
+ /// Processes purchase with comprehensive fraud detection and balance validation
+ ///
+ /// Purchase processing parameters
+ /// Purchase processing result with fraud analysis and balance impact
+ [HttpPost("process")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(409)] // Purchase blocked due to fraud or balance concerns
+ public async Task ProcessPurchase([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid purchase request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var purchaseAmount = requestDict?.ContainsKey("purchaseAmount") == true ?
+ Convert.ToDecimal(requestDict["purchaseAmount"]) : 0m;
+ var packageId = requestDict?.ContainsKey("packageId") == true ?
+ requestDict["packageId"].ToString() : "";
+ var paymentDetails = requestDict?.ContainsKey("paymentDetails") == true ?
+ (Dictionary)requestDict["paymentDetails"] : new();
+
+ var (success, transactionId, fraudAnalysis, gameImpact, balanceValidation) =
+ await _purchaseService.ProcessPurchaseAsync(
+ playerId, kingdomId, purchaseAmount, packageId, paymentDetails);
+
+ if (!success)
+ {
+ var blockingReason = fraudAnalysis?.ContainsKey("blocked") == true ? "Fraud detection" :
+ balanceValidation?.ContainsKey("blocked") == true ? "Balance protection" :
+ "Purchase validation failed";
+
+ return Conflict(new
+ {
+ Message = $"Purchase blocked - {blockingReason}",
+ Code = "PURCHASE_BLOCKED",
+ Details = new
+ {
+ FraudAnalysis = fraudAnalysis,
+ BalanceValidation = balanceValidation
+ }
+ });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ Success = success,
+ TransactionId = transactionId,
+ PurchaseAmount = purchaseAmount,
+ PackageId = packageId,
+ FraudAnalysis = fraudAnalysis,
+ GameImpact = gameImpact,
+ BalanceValidation = balanceValidation,
+ ProcessingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Purchase processed successfully for Player {PlayerId} - Transaction: {TransactionId}, Amount: {Amount}",
+ playerId, transactionId, purchaseAmount);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing purchase for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to process purchase", Code = "PURCHASE_PROCESSING_ERROR" });
+ }
+ }
+
+ ///
+ /// Validates purchase against fraud detection and spending limits
+ ///
+ /// Purchase validation parameters
+ /// Purchase validation result with risk assessment
+ [HttpPost("validate")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task ValidatePurchase([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid purchase validation request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var purchaseAmount = requestDict?.ContainsKey("purchaseAmount") == true ?
+ Convert.ToDecimal(requestDict["purchaseAmount"]) : 0m;
+ var packageId = requestDict?.ContainsKey("packageId") == true ?
+ requestDict["packageId"].ToString() : "";
+ var validationType = requestDict?.ContainsKey("validationType") == true ?
+ requestDict["validationType"].ToString() : "comprehensive";
+
+ var (canPurchase, validationResults, recommendations, protections) =
+ await _purchaseService.ValidatePurchaseAsync(
+ playerId, kingdomId, purchaseAmount, packageId, validationType);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ PurchaseAmount = purchaseAmount,
+ PackageId = packageId,
+ CanPurchase = canPurchase,
+ ValidationResults = validationResults,
+ Recommendations = recommendations,
+ PlayerProtections = protections,
+ ValidationTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating purchase for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to validate purchase", Code = "PURCHASE_VALIDATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes purchase refund with game state adjustments
+ ///
+ /// Transaction to refund
+ /// Refund processing parameters
+ /// Refund processing result with state adjustments
+ [HttpPost("{transactionId}/refund")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(404)] // Transaction not found
+ public async Task ProcessRefund(string transactionId, [FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid refund request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var refundReason = requestDict?.ContainsKey("refundReason") == true ?
+ requestDict["refundReason"].ToString() : "";
+ var refundAmount = requestDict?.ContainsKey("refundAmount") == true ?
+ Convert.ToDecimal(requestDict["refundAmount"]) : 0m;
+
+ var (success, refundId, gameAdjustments, chargebackProtection) =
+ await _purchaseService.ProcessRefundAsync(
+ playerId, kingdomId, transactionId, refundReason, refundAmount);
+
+ if (!success)
+ {
+ return NotFound(new { Message = "Transaction not found or refund not allowed", Code = "REFUND_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ TransactionId = transactionId,
+ Success = success,
+ RefundId = refundId,
+ RefundAmount = refundAmount,
+ RefundReason = refundReason,
+ GameAdjustments = gameAdjustments,
+ ChargebackProtection = chargebackProtection,
+ RefundTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Refund processed successfully for Player {PlayerId} - Transaction: {TransactionId}, Refund: {RefundId}",
+ playerId, transactionId, refundId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing refund for Transaction {TransactionId}", transactionId);
+ return StatusCode(500, new { Message = "Failed to process refund", Code = "REFUND_PROCESSING_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Anti-Pay-to-Win Monitoring
+
+ ///
+ /// Validates victory outcomes against spending influence thresholds
+ ///
+ /// Victory validation parameters
+ /// Victory validation result with spending influence analysis
+ [HttpPost("validate-victory-outcomes")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task ValidateVictoryOutcomes([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid victory validation request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var battleResults = requestDict?.ContainsKey("battleResults") == true ?
+ (Dictionary)requestDict["battleResults"] : new();
+ var timeframeDays = requestDict?.ContainsKey("timeframeDays") == true ?
+ Convert.ToInt32(requestDict["timeframeDays"]) : 30;
+
+ var validationResult = await _purchaseService.ValidateVictoryOutcomesAsync(
+ playerId, kingdomId, battleResults, timeframeDays);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ TimeframeDays = timeframeDays,
+ ValidationResult = validationResult,
+ ValidationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Victory outcomes validated for Player {PlayerId} over {Days} days",
+ playerId, timeframeDays);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating victory outcomes for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to validate victory outcomes", Code = "VICTORY_VALIDATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Monitors competitive effectiveness ensuring free players maintain viability
+ ///
+ /// Analysis timeframe (default: 30 days)
+ /// Competitive effectiveness analysis with balance recommendations
+ [HttpGet("monitor-competitive-effectiveness")]
+ [ProducesResponseType(typeof(object), 200)]
+ public async Task MonitorCompetitiveEffectiveness([FromQuery] int timeframeDays = 30)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var effectivenessAnalysis = await _purchaseService.MonitorCompetitiveEffectivenessAsync(
+ playerId, kingdomId, timeframeDays);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ TimeframeDays = timeframeDays,
+ EffectivenessAnalysis = effectivenessAnalysis,
+ MonitoringTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error monitoring competitive effectiveness for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to monitor competitive effectiveness", Code = "EFFECTIVENESS_MONITORING_ERROR" });
+ }
+ }
+
+ ///
+ /// Applies dynamic balance adjustments to maintain fair gameplay
+ ///
+ /// Balance adjustment parameters
+ /// Balance adjustment result with applied compensations
+ [HttpPost("apply-balance-adjustments")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)] // Insufficient authority for adjustments
+ public async Task ApplyBalanceAdjustments([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid balance adjustment request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var imbalanceDetection = requestDict?.ContainsKey("imbalanceDetection") == true ?
+ (Dictionary)requestDict["imbalanceDetection"] : new();
+ var compensationTypes = requestDict?.ContainsKey("compensationTypes") == true ?
+ ((System.Text.Json.JsonElement)requestDict["compensationTypes"]).Deserialize>() ?? new() : new();
+
+ var (success, appliedAdjustments, playerImpacts, systemBalance) =
+ await _purchaseService.ApplyDynamicBalanceAdjustmentsAsync(
+ playerId, kingdomId, imbalanceDetection, compensationTypes);
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Balance adjustments failed - insufficient authority or invalid adjustments", Code = "BALANCE_ADJUSTMENT_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ Success = success,
+ AppliedAdjustments = appliedAdjustments,
+ PlayerImpacts = playerImpacts,
+ SystemBalance = systemBalance,
+ AdjustmentTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Balance adjustments applied for Player {PlayerId} in Kingdom {KingdomId}",
+ playerId, kingdomId);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error applying balance adjustments for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to apply balance adjustments", Code = "BALANCE_ADJUSTMENT_ERROR" });
+ }
+ }
+
+ ///
+ /// Monitors spending dominance patterns within kingdom
+ ///
+ /// Monitoring timeframe (default: 30 days)
+ /// Spending dominance analysis with intervention recommendations
+ [HttpGet("monitor-spending-dominance")]
+ [ProducesResponseType(typeof(object), 200)]
+ public async Task MonitorSpendingDominance([FromQuery] int timeframeDays = 30)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var dominanceAnalysis = await _purchaseService.MonitorSpendingDominanceAsync(
+ playerId, kingdomId, timeframeDays);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ TimeframeDays = timeframeDays,
+ DominanceAnalysis = dominanceAnalysis,
+ MonitoringTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error monitoring spending dominance for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to monitor spending dominance", Code = "DOMINANCE_MONITORING_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region VIP System Management
+
+ ///
+ /// Processes VIP progression with secret tier handling and chargeback protection
+ ///
+ /// VIP progression parameters
+ /// VIP progression result with tier advancement and benefits
+ [HttpPost("vip/process-progression")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(409)] // VIP progression blocked
+ public async Task ProcessVipProgression([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid VIP progression request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var spendingAmount = requestDict?.ContainsKey("spendingAmount") == true ?
+ Convert.ToDecimal(requestDict["spendingAmount"]) : 0m;
+ var tierProgression = requestDict?.ContainsKey("tierProgression") == true ?
+ (Dictionary)requestDict["tierProgression"] : new();
+
+ var (success, newVipTier, secretTierUnlocked, chargebackProtection, convenienceBenefits) =
+ await _purchaseService.ProcessVipProgressionAsync(
+ playerId, kingdomId, spendingAmount, tierProgression);
+
+ if (!success)
+ {
+ return Conflict(new { Message = "VIP progression blocked - spending validation failed or chargeback risk detected", Code = "VIP_PROGRESSION_BLOCKED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ Success = success,
+ NewVipTier = newVipTier,
+ SecretTierUnlocked = secretTierUnlocked,
+ ChargebackProtection = chargebackProtection,
+ ConvenienceBenefits = convenienceBenefits,
+ ProgressionTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("VIP progression processed for Player {PlayerId} - New Tier: {Tier}, Secret: {Secret}",
+ playerId, newVipTier, secretTierUnlocked);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing VIP progression for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to process VIP progression", Code = "VIP_PROGRESSION_ERROR" });
+ }
+ }
+
+ ///
+ /// Claims VIP benefits with convenience focus and skill alternatives
+ ///
+ /// VIP benefit claim parameters
+ /// VIP benefit claim result with applied benefits
+ [HttpPost("vip/claim-benefits")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)] // Benefits not available or already claimed
+ public async Task ClaimVipBenefits([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid VIP benefit claim request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var benefitType = requestDict?.ContainsKey("benefitType") == true ?
+ requestDict["benefitType"].ToString() : "";
+ var claimParameters = requestDict?.ContainsKey("claimParameters") == true ?
+ (Dictionary)requestDict["claimParameters"] : new();
+
+ var (success, appliedBenefits, skillAlternatives, convenienceEnhancements) =
+ await _purchaseService.ClaimVipBenefitsAsync(
+ playerId, kingdomId, benefitType, claimParameters);
+
+ if (!success)
+ {
+ return Forbid(new { Message = "VIP benefit claim failed - benefits not available, already claimed, or invalid tier", Code = "BENEFIT_CLAIM_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ BenefitType = benefitType,
+ Success = success,
+ AppliedBenefits = appliedBenefits,
+ SkillAlternatives = skillAlternatives,
+ ConvenienceEnhancements = convenienceEnhancements,
+ ClaimTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("VIP benefits claimed for Player {PlayerId} - Benefit Type: {Type}",
+ playerId, benefitType);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error claiming VIP benefits for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to claim VIP benefits", Code = "VIP_BENEFIT_CLAIM_ERROR" });
+ }
+ }
+
+ ///
+ /// Validates VIP tier against chargeback protection systems
+ ///
+ /// VIP tier to validate
+ /// VIP tier validation result with chargeback risk assessment
+ [HttpGet("vip/{vipTier}/validate-protection")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task ValidateVipTierProtection(int vipTier)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var (isProtected, protectionLevel, riskFactors, mitigationStrategies) =
+ await _purchaseService.ValidateVipTierProtectionAsync(
+ playerId, kingdomId, vipTier);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ VipTier = vipTier,
+ IsProtected = isProtected,
+ ProtectionLevel = protectionLevel,
+ RiskFactors = riskFactors,
+ MitigationStrategies = mitigationStrategies,
+ ValidationTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating VIP tier protection for Player {PlayerId}, Tier: {Tier}",
+ GetAuthenticatedPlayer().PlayerId, vipTier);
+ return StatusCode(500, new { Message = "Failed to validate VIP tier protection", Code = "VIP_VALIDATION_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Skill-Based Alternatives
+
+ ///
+ /// Provides skill-based alternatives to premium features
+ ///
+ /// Skill alternative parameters
+ /// Skill-based alternative options with achievement paths
+ [HttpPost("skill-alternatives/provide")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task ProvideSkillAlternatives([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid skill alternatives request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var premiumFeature = requestDict?.ContainsKey("premiumFeature") == true ?
+ requestDict["premiumFeature"].ToString() : "";
+ var playerSkillLevel = requestDict?.ContainsKey("playerSkillLevel") == true ?
+ Convert.ToInt32(requestDict["playerSkillLevel"]) : 1;
+
+ var skillAlternatives = await _purchaseService.ProvideSkillBasedAlternativesAsync(
+ playerId, kingdomId, premiumFeature, playerSkillLevel);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ PremiumFeature = premiumFeature,
+ PlayerSkillLevel = playerSkillLevel,
+ SkillAlternatives = skillAlternatives,
+ ProvidedTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Skill alternatives provided for Player {PlayerId} - Feature: {Feature}",
+ playerId, premiumFeature);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error providing skill alternatives for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to provide skill alternatives", Code = "SKILL_ALTERNATIVES_ERROR" });
+ }
+ }
+
+ ///
+ /// Validates alternative effectiveness ensuring competitive viability
+ ///
+ /// Alternative effectiveness parameters
+ /// Alternative effectiveness validation with competitive analysis
+ [HttpPost("skill-alternatives/validate-effectiveness")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task ValidateAlternativeEffectiveness([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid effectiveness validation request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var alternativeUsage = requestDict?.ContainsKey("alternativeUsage") == true ?
+ (Dictionary)requestDict["alternativeUsage"] : new();
+ var competitiveContext = requestDict?.ContainsKey("competitiveContext") == true ?
+ (Dictionary)requestDict["competitiveContext"] : new();
+
+ var effectivenessResult = await _purchaseService.ValidateAlternativeEffectivenessAsync(
+ playerId, kingdomId, alternativeUsage, competitiveContext);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ EffectivenessResult = effectivenessResult,
+ ValidationTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error validating alternative effectiveness for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to validate alternative effectiveness", Code = "EFFECTIVENESS_VALIDATION_ERROR" });
+ }
+ }
+
+ ///
+ /// Processes achievement-based rewards as alternatives to purchases
+ ///
+ /// Achievement reward parameters
+ /// Achievement reward result with granted benefits
+ [HttpPost("skill-alternatives/process-achievement-rewards")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)] // Achievement not qualified or already claimed
+ public async Task ProcessAchievementRewards([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid achievement reward request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var achievementType = requestDict?.ContainsKey("achievementType") == true ?
+ requestDict["achievementType"].ToString() : "";
+ var achievementCriteria = requestDict?.ContainsKey("achievementCriteria") == true ?
+ (Dictionary)requestDict["achievementCriteria"] : new();
+
+ var (success, rewardDetails, competitiveValue, skillRecognition) =
+ await _purchaseService.ProcessAchievementBasedRewardsAsync(
+ playerId, kingdomId, achievementType, achievementCriteria);
+
+ if (!success)
+ {
+ return Forbid(new { Message = "Achievement reward failed - criteria not met, already claimed, or invalid achievement", Code = "ACHIEVEMENT_REWARD_FAILED" });
+ }
+
+ var response = new
+ {
+ PlayerId = playerId,
+ AchievementType = achievementType,
+ Success = success,
+ RewardDetails = rewardDetails,
+ CompetitiveValue = competitiveValue,
+ SkillRecognition = skillRecognition,
+ ProcessingTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Achievement rewards processed for Player {PlayerId} - Achievement: {Type}",
+ playerId, achievementType);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing achievement rewards for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to process achievement rewards", Code = "ACHIEVEMENT_REWARD_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Player Protection Systems
+
+ ///
+ /// Monitors player spending patterns for health and protection needs
+ ///
+ /// Monitoring timeframe (default: 30 days)
+ /// Spending pattern analysis with health recommendations
+ [HttpGet("protection/monitor-spending-patterns")]
+ [ProducesResponseType(typeof(object), 200)]
+ public async Task MonitorSpendingPatterns([FromQuery] int timeframeDays = 30)
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var spendingAnalysis = await _purchaseService.MonitorSpendingPatternsAsync(
+ playerId, kingdomId, timeframeDays);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ TimeframeDays = timeframeDays,
+ SpendingAnalysis = spendingAnalysis,
+ MonitoringTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error monitoring spending patterns for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to monitor spending patterns", Code = "SPENDING_MONITORING_ERROR" });
+ }
+ }
+
+ ///
+ /// Implements spending limits with player protection measures
+ ///
+ /// Spending limit parameters
+ /// Spending limit implementation result
+ [HttpPost("protection/implement-spending-limits")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task ImplementSpendingLimits([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid spending limits request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var limitType = requestDict?.ContainsKey("limitType") == true ?
+ requestDict["limitType"].ToString() : "daily";
+ var limitAmount = requestDict?.ContainsKey("limitAmount") == true ?
+ Convert.ToDecimal(requestDict["limitAmount"]) : 50m;
+ var protectionReason = requestDict?.ContainsKey("protectionReason") == true ?
+ requestDict["protectionReason"].ToString() : "player_welfare";
+
+ var (success, implementedLimits, monitoringEnhancements, playerNotifications) =
+ await _purchaseService.ImplementSpendingLimitsAsync(
+ playerId, kingdomId, limitType, limitAmount, protectionReason);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ Success = success,
+ LimitType = limitType,
+ LimitAmount = limitAmount,
+ ProtectionReason = protectionReason,
+ ImplementedLimits = implementedLimits,
+ MonitoringEnhancements = monitoringEnhancements,
+ PlayerNotifications = playerNotifications,
+ ImplementationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Spending limits implemented for Player {PlayerId} - Type: {Type}, Amount: {Amount}",
+ playerId, limitType, limitAmount);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error implementing spending limits for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to implement spending limits", Code = "SPENDING_LIMITS_ERROR" });
+ }
+ }
+
+ ///
+ /// Assesses player protection needs based on spending behavior
+ ///
+ /// Protection assessment parameters
+ /// Protection needs assessment with intervention recommendations
+ [HttpPost("protection/assess-needs")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ public async Task AssessPlayerProtectionNeeds([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid protection assessment request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var spendingPattern = requestDict?.ContainsKey("spendingPattern") == true ?
+ (Dictionary)requestDict["spendingPattern"] : new();
+
+ var protectionAssessment = await _purchaseService.AssessPlayerProtectionNeedsAsync(
+ playerId, kingdomId, spendingPattern);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ ProtectionAssessment = protectionAssessment,
+ AssessmentTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error assessing player protection needs for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to assess player protection needs", Code = "PROTECTION_ASSESSMENT_ERROR" });
+ }
+ }
+
+ #endregion
+
+ #region Revenue Analytics
+
+ ///
+ /// Generates ethical revenue analytics balancing business goals with player welfare
+ ///
+ /// Revenue analytics parameters
+ /// Ethical revenue analytics with sustainability insights
+ [HttpPost("analytics/ethical-revenue")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)] // Insufficient authority for revenue analytics
+ public async Task GenerateEthicalRevenueAnalytics([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid revenue analytics request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var kingdomIds = requestDict?.ContainsKey("kingdomIds") == true ?
+ ((System.Text.Json.JsonElement)requestDict["kingdomIds"]).Deserialize>() ?? new() { kingdomId } :
+ new() { kingdomId };
+ var analysisType = requestDict?.ContainsKey("analysisType") == true ?
+ requestDict["analysisType"].ToString() : "comprehensive";
+ var timeframeDays = requestDict?.ContainsKey("timeframeDays") == true ?
+ Convert.ToInt32(requestDict["timeframeDays"]) : 30;
+
+ var revenueAnalytics = await _purchaseService.GenerateEthicalRevenueAnalyticsAsync(
+ kingdomIds, analysisType, timeframeDays);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomIds = kingdomIds,
+ AnalysisType = analysisType,
+ TimeframeDays = timeframeDays,
+ RevenueAnalytics = revenueAnalytics,
+ GenerationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Ethical revenue analytics generated for Kingdoms: {KingdomIds}, Type: {Type}",
+ string.Join(",", kingdomIds), analysisType);
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error generating ethical revenue analytics for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to generate revenue analytics", Code = "REVENUE_ANALYTICS_ERROR" });
+ }
+ }
+
+ ///
+ /// Analyzes purchase conversion patterns with player satisfaction correlation
+ ///
+ /// Type of conversion to analyze (default: comprehensive)
+ /// Conversion pattern analysis with satisfaction correlation
+ [HttpGet("analytics/conversion-patterns")]
+ [ProducesResponseType(typeof(object), 200)]
+ public async Task AnalyzePurchaseConversionPatterns([FromQuery] string conversionType = "comprehensive")
+ {
+ try
+ {
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+
+ var conversionAnalysis = await _purchaseService.AnalyzePurchaseConversionPatternsAsync(
+ kingdomId, conversionType);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomId = kingdomId,
+ ConversionType = conversionType,
+ ConversionAnalysis = conversionAnalysis,
+ AnalysisTime = DateTime.UtcNow
+ };
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error analyzing conversion patterns for Kingdom {KingdomId}", GetAuthenticatedPlayer().KingdomId);
+ return StatusCode(500, new { Message = "Failed to analyze conversion patterns", Code = "CONVERSION_ANALYSIS_ERROR" });
+ }
+ }
+
+ ///
+ /// Calculates sustainable monetization metrics prioritizing long-term value
+ ///
+ /// Sustainability metrics parameters
+ /// Sustainable monetization metrics with viability assessment
+ [HttpPost("analytics/sustainable-monetization")]
+ [ProducesResponseType(typeof(object), 200)]
+ [ProducesResponseType(400)]
+ [ProducesResponseType(403)]
+ public async Task CalculateSustainableMonetizationMetrics([FromBody] object request)
+ {
+ try
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest(new { Message = "Invalid sustainability metrics request", Code = "INVALID_REQUEST" });
+ }
+
+ var (playerId, kingdomId) = GetAuthenticatedPlayer();
+ var requestDict = System.Text.Json.JsonSerializer.Deserialize>(request.ToString() ?? "{}");
+
+ var kingdomIds = requestDict?.ContainsKey("kingdomIds") == true ?
+ ((System.Text.Json.JsonElement)requestDict["kingdomIds"]).Deserialize>() ?? new() { kingdomId } :
+ new() { kingdomId };
+ var sustainabilityFactors = requestDict?.ContainsKey("sustainabilityFactors") == true ?
+ (Dictionary)requestDict["sustainabilityFactors"] : new();
+
+ var sustainabilityMetrics = await _purchaseService.CalculateSustainableMonetizationMetricsAsync(
+ kingdomIds, sustainabilityFactors);
+
+ var response = new
+ {
+ PlayerId = playerId,
+ KingdomIds = kingdomIds,
+ SustainabilityMetrics = sustainabilityMetrics,
+ CalculationTime = DateTime.UtcNow
+ };
+
+ _logger.LogInformation("Sustainable monetization metrics calculated for Kingdoms: {KingdomIds}",
+ string.Join(",", kingdomIds));
+
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error calculating sustainable monetization metrics for Player {PlayerId}", GetAuthenticatedPlayer().PlayerId);
+ return StatusCode(500, new { Message = "Failed to calculate sustainability metrics", Code = "SUSTAINABILITY_METRICS_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
+ }
+}
\ No newline at end of file
diff --git a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/ShadowedRealms.API.csproj b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/ShadowedRealms.API.csproj
index ed92f44..b765c6e 100644
--- a/ShadowedRealmsMobile/src/server/ShadowedRealms.API/ShadowedRealms.API.csproj
+++ b/ShadowedRealmsMobile/src/server/ShadowedRealms.API/ShadowedRealms.API.csproj
@@ -19,11 +19,7 @@
-
-
-
-