using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using Application.Abstractions.Notification;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SharedKernel;
namespace Infrastructure.Notification;
///
/// Nextcloud Talk OCS Chat API 운영 알림 발송기.
/// 메시지는 백그라운드로 발송(fire-and-forget) — 외부 메신저 장애가 파트너 API 응답을 지연/실패시키지 않는다.
/// 모든 메시지에 [환경명] prefix 를 붙여 dev/prod 발신원을 구분한다.
///
internal sealed class NextcloudTalkAlertService(
IHttpClientFactory httpClientFactory,
IOptions options,
IHostEnvironment env,
ILogger logger
) : IAdminAlertService
{
public Task SendActivityAsync(string message, CancellationToken ct = default)
{
QueueSend(options.Value.NextcloudTalk.ActivityRoomToken, message);
return Task.CompletedTask;
}
public Task SendAlertAsync(string message, CancellationToken ct = default)
{
QueueSend(options.Value.NextcloudTalk.AlertRoomToken, message);
return Task.CompletedTask;
}
private void QueueSend(string roomToken, string message)
{
var cfg = options.Value.NextcloudTalk;
if (!cfg.Enabled || string.IsNullOrWhiteSpace(cfg.BaseUrl) || string.IsNullOrWhiteSpace(cfg.Username) || string.IsNullOrWhiteSpace(cfg.AppPassword) || string.IsNullOrWhiteSpace(roomToken))
{
return;
}
// fire-and-forget — 호출자 CancellationToken 과 분리해 클라이언트 연결 종료에도 발송을 완료한다
var label = string.IsNullOrWhiteSpace(cfg.Label) ? env.EnvironmentName : cfg.Label;
_ = Task.Run(() => SendCoreAsync(cfg, roomToken, $"[{label}] {message}"));
}
private async Task SendCoreAsync(AppSettings.NextcloudTalkSection cfg, string roomToken, string message)
{
try
{
var client = httpClientFactory.CreateClient("NextcloudTalk");
var request = new HttpRequestMessage(HttpMethod.Post, $"{cfg.BaseUrl.TrimEnd('/')}/ocs/v2.php/apps/spreed/api/v1/chat/{roomToken}");
request.Headers.Add("OCS-APIRequest", "true");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{cfg.Username}:{cfg.AppPassword}")));
request.Content = JsonContent.Create(new { message });
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
logger.LogWarning("[NextcloudTalk] 발송 실패 — HTTP {Status}, room={Room}", (int)response.StatusCode, roomToken);
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "[NextcloudTalk] 발송 오류 — room={Room}", roomToken);
}
}
}