using System.Text.Json;
using RelayShared.Rtc;
using RelayServer.Services.Rtc;
using RelayShared.Services;

namespace RelayServer.Endpoints;

public static class RtcEndpoints
{
    /// <summary>
    /// Maps all RTC-related HTTP endpoints used for storing offers and answers,
    /// writing ICE candidates, checking active calls, and leaving active calls.
    /// </summary>
    /// <param name="app">The web application to map endpoints onto.</param>
    public static void MapRtcEndpoints(this WebApplication app)
    {
        // Store or update the current SDP offer for a channel call.
        app.MapPost("/api/rtc/offer", async (RtcOffer request, RtcCallService rtcCallService) =>
        {
            await rtcCallService.WriteOfferAsync(request.ChannelId, request.Username, request.SessionDescription);

            RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
            {
                Type = SignalType.OfferUpdated,
                ChannelId = request.ChannelId,
                Username = request.Username
            });

            return Results.Ok();
        });

        // List all offers.
        app.MapGet("/api/rtc/offers", async (RtcCallService rtcCallService) =>
        {
            return Results.Ok(await rtcCallService.GetOffersAsync());
        });

        // Return whether the specified channel currently has an active call.
        app.MapGet("/api/rtc/active/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
        {
            return Results.Ok(await rtcCallService.HasActiveCallAsync(channelId));
        });

        // Return the latest stored SDP offer for the specified channel.
        app.MapGet("/api/rtc/offers/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
        {
            var offer = await rtcCallService.GetOfferAsync(channelId);
            return offer is null ? Results.NotFound() : Results.Ok(offer);
        });

        // Store a new SDP answer for the specified channel call.
        app.MapPost("/api/rtc/answer", async (RtcOffer request, RtcCallService rtcCallService) =>
        {
            Console.WriteLine($"RTC answer received for channel {request.ChannelId} from {request.Username}");

            await rtcCallService.WriteAnswerAsync(request.ChannelId, request.SessionDescription);

            Console.WriteLine($"Broadcasting rtc_answer_updated for {request.ChannelId}");

            RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
            {
                Type = SignalType.AnswerUpdated,
                ChannelId = request.ChannelId
            });

            return Results.Ok();
        });

        // Return all answers stored for the specified channel.
        app.MapGet("/api/rtc/answers/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
        {
            return Results.Ok(await rtcCallService.GetAnswersAsync(channelId));
        });
        
        app.MapGet("/api/rtc/participants/{channelId}", (string channelId) =>
        {
            return Results.Ok(RtcChannelPresenceService.GetUsersInChannel(channelId));
        });

        // Return the latest answer stored for the specified channel.
        app.MapGet("/api/rtc/answer/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
        {
            var answer = await rtcCallService.GetLatestAnswerAsync(channelId);
            return answer is null ? Results.NotFound() : Results.Ok(answer);
        });

        // Store a new ICE candidate for the specified channel call.
        app.MapPost("/api/rtc/candidate", async (RtcIceCandidate request, RtcCallService rtcCallService) =>
        {
            await rtcCallService.WriteIceCandidateAsync(
                request.ChannelId,
                request.Username,
                request.Candidate.candidate,
                request.Candidate.sdpMid,
                request.Candidate.sdpMLineIndex
            );

            RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
            {
                Type = SignalType.CandidateAdded,
                ChannelId = request.ChannelId,
                Username = request.Username,
                Direction = JsonSerializer.Serialize(request.Candidate)
            });

            return Results.Ok();
        });

        // Return all ICE candidates stored for the specified channel.
        app.MapGet("/api/rtc/candidates/{channelId}", async (string channelId, RtcCallService rtcCallService) =>
        {
            return Results.Ok(await rtcCallService.GetIceCandidatesAsync(channelId));
        });

        // Return ICE candidates for the specified channel that belong to other users
        // and match the requested direction.
        app.MapGet("/api/rtc/candidates/{channelId}/{username}/{direction}", async (
            string channelId,
            string username,
            string direction,
            RtcCallService rtcCallService) =>
        {
            return Results.Ok(await rtcCallService.GetIceCandidatesForOthersAsync(channelId, username, direction));
        });

        // Leave the active call for the specified channel.
        app.MapPost("/api/rtc/leave", async (RtcLeaveRequest request, RtcCallService rtcCallService) =>
        {
            await rtcCallService.LeaveCallAsync(request.ChannelId, request.Username);

            RtcNotificationService.BroadcastToChannel(new RtcNotificationMessage
            {
                Type = SignalType.CallLeft,
                ChannelId = request.ChannelId,
                Username = request.Username
            });

            return Results.Ok();
        });
    }
}