// Copyright (C) 2021-present sam (starshines.gay)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

using Catalogger.Backend.Extensions;
using NodaTime;
using NodaTime.Extensions;
using Remora.Discord.API;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;

namespace Catalogger.Backend.Services;

public class NewsService(
    Config config,
    ILogger logger,
    IDiscordRestChannelAPI channelApi,
    IClock clock
)
{
    private static readonly Duration MaxNewsAge = Duration.FromDays(90); // 3 months
    private static readonly Duration ExpiresAfter = Duration.FromHours(1);

    private readonly ILogger _logger = logger.ForContext<NewsService>();
    private List<IMessage>? _messages;
    private readonly SemaphoreSlim _lock = new(1);
    private bool _isExpired => clock.GetCurrentInstant() > clock.GetCurrentInstant() + ExpiresAfter;

    public async Task<IEnumerable<NewsMessage>> GetNewsAsync()
    {
        if (_messages != null && !_isExpired)
            return _messages.Select(FormatIMessage);

        var messages = await GetRawNewsAsync();
        return messages.Select(FormatIMessage);
    }

    public void AddMessage(IMessage message) =>
        _messages = _messages?.Take(4).Prepend(message).ToList();

    private async Task<List<IMessage>> GetRawNewsAsync()
    {
        await _lock.WaitAsync();
        try
        {
            if (config.Web.NewsChannel == null)
                return [];

            _logger.Information("Fetching news from channel {ChannelId}", config.Web.NewsChannel);

            var res = await channelApi.GetChannelMessagesAsync(
                DiscordSnowflake.New(config.Web.NewsChannel.Value),
                limit: 5
            );
            if (res.IsSuccess)
                return _messages = res
                    .Entity.Where(m =>
                        m.ID.Timestamp.ToInstant() > clock.GetCurrentInstant() - MaxNewsAge
                    )
                    .ToList();

            return [];
        }
        finally
        {
            _lock.Release();
        }
    }

    public record NewsMessage(
        string Author,
        string Content,
        IEnumerable<string> AttachmentUrls,
        DateTimeOffset PostedAt,
        DateTimeOffset? EditedAt
    );

    private NewsMessage FormatIMessage(IMessage msg) =>
        new(
            msg.Author.Tag(),
            msg.Content,
            msg.Attachments.Select(a => a.Url),
            msg.ID.Timestamp,
            msg.EditedTimestamp
        );
}
