using System.Text.Json; using License.Api.Data; using License.Api.DTOs; using License.Api.Models; using License.Api.Services; using License.Api.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace License.Api.Controllers; [ApiController] [Authorize(Policy = "SuperAdmin")] [Route("api/admin/agents")] public class AdminAgentsController : ControllerBase { private readonly AppDbContext _db; private readonly ConfigService _config; public AdminAgentsController(AppDbContext db, ConfigService config) { _db = db; _config = config; } [HttpPost] public async Task Create([FromBody] AgentCreateRequest request) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var agent = new Agent { AdminId = request.AdminId, AgentCode = request.AgentCode, CompanyName = request.CompanyName, ContactPerson = request.ContactPerson, ContactPhone = request.ContactPhone, ContactEmail = request.ContactEmail, PasswordHash = PasswordHasher.Hash(request.Password), Balance = request.InitialBalance, Discount = request.Discount, CreditLimit = request.CreditLimit, MaxProjects = request.AllowedProjects?.Count ?? 0, AllowedProjects = request.AllowedProjects == null ? null : JsonSerializer.Serialize(request.AllowedProjects), Status = "active", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; _db.Agents.Add(agent); await _db.SaveChangesAsync(); var data = new AgentDetailResponse { Id = agent.Id, AgentCode = agent.AgentCode, CompanyName = agent.CompanyName, ContactPerson = agent.ContactPerson, ContactPhone = agent.ContactPhone, ContactEmail = agent.ContactEmail, Balance = agent.Balance, Discount = agent.Discount, CreditLimit = agent.CreditLimit, Status = agent.Status, CreatedAt = agent.CreatedAt }; return Ok(ApiResponse.Ok(data)); } [HttpGet] public async Task List([FromQuery] string? status, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); page = Math.Max(1, page); pageSize = Math.Clamp(pageSize, 1, 100); var query = _db.Agents.AsQueryable(); if (!string.IsNullOrWhiteSpace(status)) query = query.Where(a => a.Status == status); var total = await query.CountAsync(); var items = await query.OrderByDescending(a => a.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(a => new AgentListItem { Id = a.Id, AgentCode = a.AgentCode, CompanyName = a.CompanyName, ContactPerson = a.ContactPerson, ContactPhone = a.ContactPhone, Balance = a.Balance, Discount = a.Discount, Status = a.Status, CreatedAt = a.CreatedAt }) .ToListAsync(); var result = new PagedResult { Items = items, Pagination = new PaginationInfo { Page = page, PageSize = pageSize, Total = total, TotalPages = (int)Math.Ceiling(total / (double)pageSize) } }; return Ok(ApiResponse>.Ok(result)); } [HttpGet("{id:int}")] public async Task Get(int id) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var agent = await _db.Agents.FindAsync(id); if (agent == null) return NotFound(ApiResponse.Fail(404, "not_found")); var transactions = await _db.AgentTransactions .Where(t => t.AgentId == id) .OrderByDescending(t => t.CreatedAt) .Take(50) .ToListAsync(); var stats = await _db.CardKeys .Where(c => c.AgentId == id && c.DeletedAt == null) .GroupBy(c => c.AgentId) .Select(g => new { totalCards = g.Count(), activeCards = g.Count(x => x.Status == "active"), totalRevenue = g.Sum(x => x.SoldPrice ?? 0) }) .SingleOrDefaultAsync(); return Ok(ApiResponse.Ok(new { agent = new AgentDetailResponse { Id = agent.Id, AgentCode = agent.AgentCode, CompanyName = agent.CompanyName, ContactPerson = agent.ContactPerson, ContactPhone = agent.ContactPhone, ContactEmail = agent.ContactEmail, Balance = agent.Balance, Discount = agent.Discount, CreditLimit = agent.CreditLimit, Status = agent.Status, CreatedAt = agent.CreatedAt }, stats, transactions })); } [HttpPut("{id:int}")] public async Task Update(int id, [FromBody] AgentUpdateRequest request) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var agent = await _db.Agents.FindAsync(id); if (agent == null) return NotFound(ApiResponse.Fail(404, "not_found")); if (!string.IsNullOrWhiteSpace(request.CompanyName)) agent.CompanyName = request.CompanyName; if (!string.IsNullOrWhiteSpace(request.ContactPerson)) agent.ContactPerson = request.ContactPerson; if (!string.IsNullOrWhiteSpace(request.ContactPhone)) agent.ContactPhone = request.ContactPhone; if (!string.IsNullOrWhiteSpace(request.ContactEmail)) agent.ContactEmail = request.ContactEmail; if (request.Discount.HasValue) agent.Discount = request.Discount.Value; if (request.CreditLimit.HasValue) agent.CreditLimit = request.CreditLimit.Value; if (request.AllowedProjects != null) { agent.AllowedProjects = JsonSerializer.Serialize(request.AllowedProjects); agent.MaxProjects = request.AllowedProjects.Count; } if (!string.IsNullOrWhiteSpace(request.Status)) agent.Status = request.Status; agent.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); return Ok(ApiResponse.Ok()); } [HttpPost("{id:int}/disable")] public async Task Disable(int id) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var agent = await _db.Agents.FindAsync(id); if (agent == null) return NotFound(ApiResponse.Fail(404, "not_found")); agent.Status = "disabled"; agent.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); return Ok(ApiResponse.Ok()); } [HttpPost("{id:int}/enable")] public async Task Enable(int id) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var agent = await _db.Agents.FindAsync(id); if (agent == null) return NotFound(ApiResponse.Fail(404, "not_found")); agent.Status = "active"; agent.UpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); return Ok(ApiResponse.Ok()); } [HttpDelete("{id:int}")] public async Task Delete(int id) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var agent = await _db.Agents.FindAsync(id); if (agent == null) return NotFound(ApiResponse.Fail(404, "not_found")); _db.Agents.Remove(agent); await _db.SaveChangesAsync(); return Ok(ApiResponse.Ok()); } [HttpPost("{id:int}/recharge")] public async Task Recharge(int id, [FromBody] AgentBalanceRequest request) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); return await AdjustBalance(id, request.Amount, "recharge", request.Remark); } [HttpPost("{id:int}/deduct")] public async Task Deduct(int id, [FromBody] AgentBalanceRequest request) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); return await AdjustBalance(id, -Math.Abs(request.Amount), "consume", request.Remark); } [HttpGet("{id:int}/transactions")] public async Task Transactions(int id) { var agentSystemEnabled = await _config.GetBoolAsync("feature.agent_system", true); if (!agentSystemEnabled) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); var items = await _db.AgentTransactions .Where(t => t.AgentId == id) .OrderByDescending(t => t.CreatedAt) .ToListAsync(); return Ok(ApiResponse>.Ok(items)); } private async Task AdjustBalance(int id, decimal amount, string type, string? remark) { if (!User.TryGetUserId(out var adminId)) return Unauthorized(ApiResponse.Fail(401, "unauthorized")); await using var tx = await _db.Database.BeginTransactionAsync(); var agent = await _db.Agents .FromSqlRaw("SELECT * FROM \"Agents\" WHERE \"Id\" = {0} FOR UPDATE", id) .FirstOrDefaultAsync(); if (agent == null) return NotFound(ApiResponse.Fail(404, "not_found")); var balanceBefore = agent.Balance; var balanceAfter = balanceBefore + amount; if (balanceAfter < -agent.CreditLimit) return StatusCode(StatusCodes.Status403Forbidden, ApiResponse.Fail(403, "forbidden")); agent.Balance = balanceAfter; agent.UpdatedAt = DateTime.UtcNow; _db.AgentTransactions.Add(new AgentTransaction { AgentId = agent.Id, Type = type, Amount = amount, BalanceBefore = balanceBefore, BalanceAfter = balanceAfter, Remark = remark, CreatedBy = adminId, CreatedAt = DateTime.UtcNow }); await _db.SaveChangesAsync(); await tx.CommitAsync(); return Ok(ApiResponse.Ok()); } }