// 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 System.Net;
using System.Threading.RateLimiting;
using Humanizer;
using NodaTime;
using Polly;

namespace Catalogger.Backend.Services;

public class PluralkitApiService(ILogger logger)
{
    private const string UserAgent = "Catalogger.NET (https://codeberg.org/starshine/catalogger)";
    private const string ApiBaseUrl = "https://api.pluralkit.me/v2";
    private readonly HttpClient _client = new();
    private readonly ILogger _logger = logger.ForContext<PluralkitApiService>();

    private readonly ResiliencePipeline _pipeline = new ResiliencePipelineBuilder()
        .AddRateLimiter(
            new FixedWindowRateLimiter(
                new FixedWindowRateLimiterOptions
                {
                    Window = 1.Seconds(),
                    PermitLimit = 2,
                    QueueLimit = 64,
                }
            )
        )
        .AddTimeout(20.Seconds())
        .Build();

    private async Task<T?> DoRequestAsync<T>(
        string path,
        bool allowNotFound = false,
        CancellationToken ct = default
    )
        where T : class
    {
        var req = new HttpRequestMessage(HttpMethod.Get, $"{ApiBaseUrl}{path}");
        req.Headers.Add("User-Agent", UserAgent);

        _logger.Debug("Requesting {Path} from PluralKit API", path);

        var resp = await _pipeline.ExecuteAsync(async ct2 => await _client.SendAsync(req, ct2), ct);
        if (resp.StatusCode == HttpStatusCode.NotFound && allowNotFound)
        {
            _logger.Debug("PluralKit API path {Path} returned 404 but 404 response is valid", path);
            return null;
        }

        if (!resp.IsSuccessStatusCode)
        {
            _logger.Error(
                "Received non-200 status code {StatusCode} from PluralKit API path {Path}",
                resp.StatusCode,
                req
            );
            throw new CataloggerError("Non-200 status code from PluralKit API");
        }

        return await resp.Content.ReadFromJsonAsync<T>(JsonUtils.ApiJsonOptions, ct)
            ?? throw new CataloggerError("JSON response from PluralKit API was null");
    }

    public async Task<PkMessage?> GetPluralKitMessageAsync(
        ulong id,
        CancellationToken ct = default
    ) => await DoRequestAsync<PkMessage>($"/messages/{id}", allowNotFound: true, ct);

    public async Task<PkSystem?> GetPluralKitSystemAsync(
        ulong id,
        CancellationToken ct = default
    ) => await DoRequestAsync<PkSystem>($"/systems/{id}", allowNotFound: true, ct);

    public record PkMessage(
        ulong Id,
        ulong Original,
        ulong Sender,
        ulong Channel,
        ulong Guild,
        PkSystem? System,
        PkMember? Member
    );

    public record PkSystem(string Id, Guid Uuid, string? Name, string? Tag, Instant? Created);

    public record PkMember(string Id, Guid Uuid, string Name, string? DisplayName);
}
