using IntelligenceApi.Engines; using IntelligenceApi.Models; using Microsoft.AspNetCore.Mvc; namespace IntelligenceApi.Controllers; /// /// Spend distribution endpoint — the single public surface of IntelligenceApi. /// /// Called exclusively by the Gateway (never directly by the client portal). /// The Gateway injects clientCategory from ClientContext before forwarding. /// /// POST /api/spend-distribution /// [ApiController] [Route("api/spend-distribution")] public sealed class SpendDistributionController : ControllerBase { private readonly EngineRouter _router; private readonly ILogger _log; public SpendDistributionController(EngineRouter router, ILogger log) { _router = router; _log = log; } /// /// Generate a spend distribution recommendation. /// clientCategory in the request body determines which engine runs. /// [HttpPost] public async Task Recommend( [FromBody] SpendDistributionRequest? request, CancellationToken ct) { if (request == null) return BadRequest(new { ok = false, error = "Request body required" }); if (request.MonthlyBudget <= 0) return BadRequest(new { ok = false, error = "monthlyBudget must be greater than zero" }); if (request.Keywords.Count == 0) return BadRequest(new { ok = false, error = "At least one keyword is required" }); _log.LogInformation( "[SpendDistribution] Request | Category={Category} Budget={Budget} Objective={Obj}", request.ClientCategory, request.MonthlyBudget, request.Objective); try { var engine = _router.Resolve(request.ClientCategory); var response = await engine.RecommendAsync(request, ct); _log.LogInformation( "[SpendDistribution] OK | Engine={Engine} Channels={N}", response.Metadata.Engine, response.Channels.Count); return Ok(response); } catch (Exception ex) { _log.LogError(ex, "[SpendDistribution] Unhandled error"); return StatusCode(500, new { ok = false, error = "Intelligence service error" }); } } }