using System.Net; using System.Text.Json; using AutopilotMonitor.Functions.Services; using AutopilotMonitor.Shared.DataAccess; using AutopilotMonitor.Shared.Models.Config; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; namespace AutopilotMonitor.Functions.Functions.Config { public class PlanManagementFunction { private readonly ILogger _logger; private readonly IConfigRepository _configRepo; private readonly AdminConfigurationService _adminConfigService; public PlanManagementFunction( ILogger logger, IConfigRepository configRepo, AdminConfigurationService adminConfigService) { _logger = logger; _configRepo = configRepo; _adminConfigService = adminConfigService; } /// /// PATCH /api/config/{tenantId}/plan /// Sets the plan tier for a tenant. Body: { "planTier": "pro" } /// [Function("SetTenantPlanTier")] public async Task SetPlanTier( [HttpTrigger(AuthorizationLevel.Anonymous, "patch", Route = "config/{tenantId}/plan")] HttpRequestData req, string tenantId) { _logger.LogInformation("SetTenantPlanTier: tenantId={TenantId}", tenantId); try { var body = await req.ReadFromJsonAsync(); if (body == null || string.IsNullOrWhiteSpace(body.PlanTier)) { var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); await badRequest.WriteAsJsonAsync(new { error = "planTier is required" }); return badRequest; } var validTiers = new[] { "free", "pro", "enterprise" }; if (!validTiers.Contains(body.PlanTier.ToLowerInvariant())) { var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); await badRequest.WriteAsJsonAsync(new { error = $"Invalid planTier. Valid values: {string.Join(", ", validTiers)}" }); return badRequest; } var config = await _configRepo.GetTenantConfigurationAsync(tenantId); if (config == null) { var notFound = req.CreateResponse(HttpStatusCode.NotFound); await notFound.WriteAsJsonAsync(new { error = "Tenant not found" }); return notFound; } config.PlanTier = body.PlanTier.ToLowerInvariant(); await _configRepo.SaveTenantConfigurationAsync(config); var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { tenantId, planTier = config.PlanTier }); return response; } catch (Exception ex) { _logger.LogError(ex, "Error setting plan tier"); var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError); await errorResponse.WriteAsJsonAsync(new { error = "Internal server error" }); return errorResponse; } } /// /// GET /api/global/config/plan-tiers /// Returns plan tier definitions from AdminConfiguration.PlanTierDefinitionsJson. /// [Function("GetPlanTierDefinitions")] public async Task GetPlanTierDefinitions( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "global/config/plan-tiers")] HttpRequestData req) { try { var config = await _adminConfigService.GetConfigurationAsync(); var tiers = ParsePlanTierDefinitions(config.PlanTierDefinitionsJson); var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { tiers }); return response; } catch (Exception ex) { _logger.LogError(ex, "Error getting plan tier definitions"); var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError); await errorResponse.WriteAsJsonAsync(new { error = "Internal server error" }); return errorResponse; } } /// /// PUT /api/global/config/plan-tiers /// Saves plan tier definitions. Body: { "tiers": [...] } /// [Function("SetPlanTierDefinitions")] public async Task SetPlanTierDefinitions( [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "global/config/plan-tiers")] HttpRequestData req) { try { var body = await req.ReadFromJsonAsync(); if (body?.Tiers == null || body.Tiers.Count == 0) { var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); await badRequest.WriteAsJsonAsync(new { error = "At least one tier definition is required" }); return badRequest; } // Validate tier names are unique var names = body.Tiers.Select(t => t.Name.ToLowerInvariant()).ToList(); if (names.Distinct().Count() != names.Count) { var badRequest = req.CreateResponse(HttpStatusCode.BadRequest); await badRequest.WriteAsJsonAsync(new { error = "Tier names must be unique" }); return badRequest; } // Normalize names to lowercase foreach (var tier in body.Tiers) tier.Name = tier.Name.ToLowerInvariant(); var config = await _adminConfigService.GetConfigurationAsync(); config.PlanTierDefinitionsJson = JsonSerializer.Serialize(body.Tiers); await _adminConfigService.SaveConfigurationAsync(config); var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { tiers = body.Tiers }); return response; } catch (Exception ex) { _logger.LogError(ex, "Error saving plan tier definitions"); var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError); await errorResponse.WriteAsJsonAsync(new { error = "Internal server error" }); return errorResponse; } } private static List ParsePlanTierDefinitions(string? json) { if (string.IsNullOrWhiteSpace(json)) return new List(); try { return JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); } catch { return new List(); } } private class SetPlanTierRequest { public string PlanTier { get; set; } = string.Empty; } private class SetPlanTierDefinitionsRequest { public List Tiers { get; set; } = new(); } } }