// 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.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;
using Catalogger.Backend.Api.Middleware;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database.Redis;
using Microsoft.AspNetCore.Mvc;

namespace Catalogger.Backend.Api;

[Route("/api")]
public class AuthController(
    Config config,
    RedisService redisService,
    GuildCache guildCache,
    ApiCache apiCache,
    DiscordRequestService discordRequestService
) : ApiControllerBase
{
    private static string StateKey(string state) => $"state:{state}";

    [HttpGet("authorize")]
    public async Task<IActionResult> GenerateAuthUrlAsync()
    {
        var state = ApiUtils.RandomToken();
        await redisService.SetStringAsync(StateKey(state), state, TimeSpan.FromMinutes(30));

        var url =
            $"https://discord.com/oauth2/authorize?response_type=code"
            + $"&client_id={config.Discord.ApplicationId}&scope=identify+guilds"
            + $"&prompt=none&state={state}"
            + $"&redirect_uri={HttpUtility.UrlEncode($"{config.Web.BaseUrl}/callback")}";

        return Redirect(url);
    }

    [HttpGet("add-guild/{id}")]
    public IActionResult AddGuild(ulong id)
    {
        var url =
            $"https://discord.com/oauth2/authorize?client_id={config.Discord.ApplicationId}"
            + "&permissions=537250993&scope=bot+applications.commands"
            + $"&guild_id={id}";

        return Redirect(url);
    }

    [HttpPost("callback")]
    [ProducesResponseType<CallbackResponse>(statusCode: StatusCodes.Status200OK)]
    public async Task<IActionResult> CallbackAsync([FromBody] CallbackRequest req)
    {
        var redisState = await redisService.GetStringAsync(StateKey(req.State), delete: true);
        if (redisState != req.State)
            throw new ApiError(
                HttpStatusCode.BadRequest,
                ErrorCode.BadRequest,
                "Invalid OAuth state"
            );

        var (token, user, guilds) = await discordRequestService.RequestDiscordTokenAsync(req.Code);
        await apiCache.SetUserAsync(user);
        await apiCache.SetGuildsAsync(user.Id, guilds);

        return Ok(
            new CallbackResponse(
                new ApiUser(user),
                guilds
                    .Where(g => g.CanManage)
                    .Select(g => new ApiGuild(g, guildCache.Contains(g.Id))),
                token.DashboardToken
            )
        );
    }

    public record CallbackRequest(string Code, string State);

    private record CallbackResponse(ApiUser User, IEnumerable<ApiGuild> Guilds, string Token);

    [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
    private record DiscordTokenResponse(string AccessToken, string? RefreshToken, int ExpiresIn);
}
