using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RMuseum.Models.Ganjoor.ViewModels;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace GanjooRazor.Utils
{
///
/// Fetches the poet list and individual poet details from the Ganjoor API, optionally caching
/// the results in-memory. This logic (fetch-or-return-cached, deserialize, cache if enabled) was
/// previously copy-pasted as three near-identical private methods (a "get all poets" fetcher, a
/// "get one poet by id and store on Model.Poet" fetcher, and an "get one poet by id and return as
/// JSON" AJAX handler) in IndexModel, ContribsModel, HashiehaModel, SearchModel, and SimiModel.
///
/// Registered as a scoped service in Startup.cs; inject via constructor like any other service.
///
public class PoetCacheService
{
private readonly HttpClient _httpClient;
private readonly IMemoryCache _memoryCache;
public PoetCacheService(HttpClient httpClient, IMemoryCache memoryCache)
{
_httpClient = httpClient;
_memoryCache = memoryCache;
}
///
/// Gets the full poet list. Returns (true, poets, null) on success, or (false, null, error)
/// on failure - callers decide what to do with the error (set LastError and return Page(),
/// return BadRequest(error), etc.) since that varies per caller.
///
/// Caching is on by default (poet metadata changes rarely and isn't personalized, so this
/// never makes an ordinary visitor - logged in or not - see stale content). Pass
/// = true (callers pass their page's EditorCacheBypass,
/// i.e. the CanEdit cookie) so an editor sees their own just-made poet edits immediately
/// instead of waiting out the cache TTL.
///
public async Task<(bool success, List poets, string error)> GetPoetsAsync(bool bypassCache = false)
{
const string cacheKey = "/api/ganjoor/poets";
if (!bypassCache && _memoryCache.TryGetValue(cacheKey, out List poets))
{
return (true, poets, null);
}
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/poets");
if (!response.IsSuccessStatusCode)
{
return (false, null, JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()));
}
poets = JArray.Parse(await response.Content.ReadAsStringAsync()).ToObject>();
if (!bypassCache)
{
_memoryCache.Set(cacheKey, poets, TimeSpan.FromHours(1));
}
return (true, poets, null);
}
///
/// Gets a single poet's full details by id. Returns (true, poet, null) on success, or
/// (false, null, error) on failure. See for the caching/bypass
/// rules - identical here.
///
public async Task<(bool success, GanjoorPoetCompleteViewModel poet, string error)> GetPoetAsync(int poetId, bool bypassCache = false)
{
var cacheKey = $"/api/ganjoor/poet/{poetId}";
if (!bypassCache && _memoryCache.TryGetValue(cacheKey, out GanjoorPoetCompleteViewModel poet))
{
return (true, poet, null);
}
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/ganjoor/poet/{poetId}");
if (!response.IsSuccessStatusCode)
{
return (false, null, JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()));
}
poet = JObject.Parse(await response.Content.ReadAsStringAsync()).ToObject();
if (!bypassCache)
{
_memoryCache.Set(cacheKey, poet, TimeSpan.FromHours(1));
}
return (true, poet, null);
}
}
}