using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Localization;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
using Volo.Abp.Localization;
using Volo.Abp.Timing;
using Volo.Abp.Users;
namespace Sozsoft.Mcp;
///
/// Gelistirme talebinin Timesheets listesini MCP islemlerinden doldurur. Talep
/// get_dev_request / start_dev_request ile acildiginda kullanicinin etkin talebi
/// olur; ardindan yapilan her yazma islemi o talebe bir satir ekler. Satir modelin dikkatine
/// birakilmaz: aciklama dil anahtarindan Turkce uretilir, sure son etkinlikten bu yana olculur.
///
public interface IMcpDevRequestTimesheetRecorder
{
/// Talebi kullanicinin etkin talebi yapar ve olcumu baslatir.
Task BeginAsync(Guid devRequestId);
///
/// Etkin talebe bir satir yazar; etkin talep yoksa (islem bir talep dongusunun parcasi
/// degilse) hicbir sey yapmaz.
///
Task RecordAsync(string descriptionKey, params object[] args);
}
public class McpDevRequestTimesheetRecorder(
IDistributedCache activityCache,
IRepository devRequestRepository,
IStringLocalizer localizer,
ICurrentUser currentUser,
IClock clock,
IGuidGenerator guidGenerator,
ILogger logger) : IMcpDevRequestTimesheetRecorder, ITransientDependency
{
/// Timesheet satirlarinin dili; talep ekrani Turkce kullanilir.
private const string TimesheetCulture = "tr";
/// Talep ekranindaki tarih kutusunun okudugu bicim.
private const string DateFormat = "yyyy-MM-dd";
///
/// Etkin talebin omru bir calisma oturumunu kapsar; suresi dolan kayit, gunler once acilmis
/// bir talebe baska bir isin yazilmasini ve sahte uzun sureyi engeller.
///
private static readonly TimeSpan ActivityLifetime = TimeSpan.FromHours(12);
public Task BeginAsync(Guid devRequestId) => StampAsync(devRequestId, clock.Now);
public async Task RecordAsync(string descriptionKey, params object[] args)
{
var activity = await activityCache.GetAsync(ActivityKey());
if (activity is null)
return;
var devRequest = await devRequestRepository.FindAsync(activity.RequestId);
if (devRequest is null)
return;
JsonArray timesheets;
try
{
timesheets = string.IsNullOrWhiteSpace(devRequest.Timesheets)
? []
: JsonNode.Parse(devRequest.Timesheets) as JsonArray ?? throw new JsonException();
}
catch (JsonException)
{
// Yapilan is (deploy, tablo) zaten tamamlandi; bozuk bir timesheet onu geri almamali.
logger.LogWarning("Timesheets of development request {RequestId} could not be read; entry skipped.", devRequest.Id);
return;
}
string description;
using (CultureHelper.Use(TimesheetCulture))
{
description = localizer[descriptionKey, args];
}
var now = clock.Now;
var minutes = Math.Max(1, (int)Math.Round((now - activity.At).TotalMinutes));
timesheets.Add(new JsonObject
{
["id"] = guidGenerator.Create().ToString(),
["date"] = now.ToString(DateFormat, CultureInfo.InvariantCulture),
["userName"] = currentUser.UserName ?? string.Empty,
["description"] = description,
// Ekranin sure kutusu saati ondalik sayi olarak okur.
["duration"] = Math.Round(minutes / 60d, 4)
});
devRequest.Timesheets = timesheets.ToJsonString();
await devRequestRepository.UpdateAsync(devRequest, autoSave: true);
await StampAsync(devRequest.Id, now);
}
private string ActivityKey() => currentUser.Id?.ToString() ?? string.Empty;
private Task StampAsync(Guid devRequestId, DateTime at) =>
activityCache.SetAsync(ActivityKey(), new McpDevRequestActivityCacheItem { RequestId = devRequestId, At = at },
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ActivityLifetime });
}