using RelayShared.Rtc;
using SurrealDb.Net;
using RelayShared.Rtc;

namespace RelayServer.Services.Rtc;

public sealed class RtcCallService
{
    private readonly SurrealDbClient _db;

    public RtcCallService(SurrealDbClient db)
    {
        _db = db;
    }

    /// <summary>
    /// Checks whether the specified channel currently has an active RTC call.
    /// </summary>
    /// <param name="channelId">The channel to inspect.</param>
    /// <returns>
    /// True if the channel has an active call; otherwise, false.
    /// </returns>
    public async Task<bool> HasActiveCallAsync(string channelId)
    {
        var activeCalls = await _db.Select<DBActiveCall>("rtc_active_calls");
        return activeCalls.Any(x => x.ChannelId == channelId && x.IsActive);
    }
    
    public async Task<DBActiveCall?> GetActiveCallAsync(string channelId)
    {
        var activeCalls = await _db.Select<DBActiveCall>("rtc_active_calls");
        return activeCalls
            .Where(x => x.ChannelId == channelId && x.IsActive)
            .OrderByDescending(x => x.UpdatedAt)
            .FirstOrDefault();
    }

    /// <summary>
    /// Creates or updates the current SDP offer for a user in the specified channel.
    /// If no active call exists for the channel, a new active call is created.
    /// Otherwise, the existing active call timestamp is refreshed.
    /// </summary>
    /// <param name="channelId">The channel the offer belongs to.</param>
    /// <param name="username">The user creating the offer.</param>
    /// <param name="type">The RtcSession Type. </param>
    /// <param name="sdp">The SDP offer payload.</param>
    public async Task WriteOfferAsync(string channelId, string username, RtcSessionDescription sessionDescription)
    {
        var activeCalls = await _db.Select<DBActiveCall>("rtc_active_calls");
        var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive);

        if (activeCall is null)
        {
            await _db.Create("rtc_active_calls", new DBActiveCall
            {
                ChannelId = channelId,
                OfferUser = username,
                Offer = new RtcSessionDescription
                {
                    Type = sessionDescription.Type,
                    Sdp = sessionDescription.Sdp
                },
                Answer = null,
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTime.UtcNow,
                IsActive = true
            });

            return;
        }

        activeCall.OfferUser = username;
        activeCall.Offer = new RtcSessionDescription
        {
            Type = sessionDescription.Type,
            Sdp = sessionDescription.Sdp
        };
        activeCall.UpdatedAt = DateTime.UtcNow;

        await _db.Merge<DBActiveCall, DBActiveCall>(activeCall);
    }

    /// <summary>
    /// Gets the current offer stored on the active call for the specified channel.
    /// </summary>
    /// <param name="channelId">The channel whose offer should be retrieved.</param>
    /// <returns>
    /// The current offer for the active call, or null if no active call or offer exists.
    /// </returns>
    public async Task<RtcSessionDescription?> GetOfferAsync(string channelId)
    {
        var activeCall = await GetActiveCallAsync(channelId);
        return activeCall?.Offer;
    }

    /// <summary>
    /// Writes a new SDP answer for the specified channel and refreshes the active call timestamp
    /// when a matching active call exists.
    /// </summary>
    /// <param name="channelId">The channel the answer belongs to.</param>
    /// <param name="offerUser">The original offer owner.</param>
    /// <param name="sessionDescription">The SDP and type answer payload.</param>
    public async Task WriteAnswerAsync(string channelId, RtcSessionDescription sessionDescription)
    {
        var activeCalls = await _db.Select<DBActiveCall>("rtc_active_calls");
        var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive);

        if (activeCall is null)
            return;

        activeCall.Answer = new RtcSessionDescription
        {
            Type = sessionDescription.Type,
            Sdp = sessionDescription.Sdp
        };
        activeCall.UpdatedAt = DateTime.UtcNow;

        await _db.Merge<DBActiveCall, DBActiveCall>(activeCall);
    }

    /// <summary>
    /// Gets all answers stored for the specified channel in creation order.
    /// </summary>
    /// <param name="channelId">The channel whose answers should be retrieved.</param>
    /// <returns>
    /// A list of answers for the channel ordered from oldest to newest.
    /// </returns>
    public async Task<List<RtcSessionDescription>> GetAnswersAsync(string channelId)
    {
        var activeCall = await GetActiveCallAsync(channelId);

        if (activeCall?.Answer is null)
            return [];

        return [activeCall.Answer];
    }

    /// <summary>
    /// Gets the most recent answer stored for the specified channel.
    /// </summary>
    /// <param name="channelId">The channel whose latest answer should be retrieved.</param>
    /// <returns>
    /// The newest answer for the channel, or null if no answer exists.
    /// </returns>
    public async Task<RtcSessionDescription?> GetLatestAnswerAsync(string channelId)
    {
        var activeCall = await GetActiveCallAsync(channelId);
        return activeCall?.Answer;
    }

    /// <summary>
    /// Writes a new ICE candidate entry for the specified channel and user.
    /// </summary>
    /// <param name="channelId">The channel the ICE candidate belongs to.</param>
    /// <param name="username">The user who produced the ICE candidate.</param>
    /// <param name="candidate">The ICE candidate string.</param>
    /// <param name="sdpMid">The SDP media identifier for the candidate, if any.</param>
    /// <param name="sdpMLineIndex">The SDP media line index for the candidate, if any.</param>
    /// <param name="direction">
    /// The signaling direction the candidate belongs to, such as offer or answer.
    /// </param>
    public async Task WriteIceCandidateAsync(
        string channelId,
        string username,
        string candidate,
        string? sdpMid,
        int? sdpMLineIndex/*,
        string direction*/)
    {
        await _db.Create("rtc_ice_candidates", new DBIceCandidate
        {
            ChannelId = channelId,
            Username = username,
            Candidate = candidate,
            SdpMid = sdpMid,
            SdpMLineIndex = sdpMLineIndex,
            // Direction = direction,
            CreatedAt = DateTime.UtcNow
        });
    }

    /// <summary>
    /// Gets all ICE candidates stored for the specified channel in creation order.
    /// </summary>
    /// <param name="channelId">The channel whose ICE candidates should be retrieved.</param>
    /// <returns>
    /// A list of ICE candidates for the channel ordered from oldest to newest.
    /// </returns>
    public async Task<List<DBIceCandidate>> GetIceCandidatesAsync(string channelId)
    {
        var candidates = await _db.Select<DBIceCandidate>("rtc_ice_candidates");
        return candidates
            .Where(x => x.ChannelId == channelId)
            .OrderBy(x => x.CreatedAt)
            .ToList();
    }

    /// <summary>
    /// Gets ICE candidates for the specified channel that were created by other users
    /// and match the requested signaling direction.
    /// </summary>
    /// <param name="channelId">The channel whose ICE candidates should be retrieved.</param>
    /// <param name="username">The user to exclude from the results.</param>
    /// <param name="direction">The signaling direction to match.</param>
    /// <returns>
    /// A list of matching ICE candidates ordered from oldest to newest.
    /// </returns>
    public async Task<List<DBIceCandidate>> GetIceCandidatesForOthersAsync(string channelId, string username, string direction)
    {
        var candidates = await _db.Select<DBIceCandidate>("rtc_ice_candidates");
        return candidates
            .Where(x => x.ChannelId == channelId && x.Username != username /*&& x.Direction == direction*/)
            .OrderBy(x => x.CreatedAt)
            .ToList();
    }

    /// <summary>
    /// Leaves the active call for the specified channel. In the current implementation,
    /// the call is only marked inactive when the offer user leaves.
    /// </summary>
    /// <param name="channelId">The channel whose call should be left.</param>
    /// <param name="username">The user leaving the call.</param>
    public async Task LeaveCallAsync(string channelId, string username)
    {
        var activeCalls = await _db.Select<DBActiveCall>("rtc_active_calls");
        var activeCall = activeCalls.FirstOrDefault(x => x.ChannelId == channelId && x.IsActive);

        if (activeCall is null)
            return;

        if (activeCall.OfferUser == username)
        {
            activeCall.IsActive = false;
            activeCall.UpdatedAt = DateTime.UtcNow;
            await _db.Merge<DBActiveCall, DBActiveCall>(activeCall);
        }
    }

    /// <summary>
    /// Gets all active call records that currently contain an offer.
    /// </summary>
    /// <returns>
    /// A list of active calls with offers, ordered from newest to oldest.
    /// </returns>
    public async Task<List<DBActiveCall>> GetOffersAsync()
    {
        var activeCalls = await _db.Select<DBActiveCall>("rtc_active_calls");
        return activeCalls
            .Where(x => x.Offer is not null)
            .OrderByDescending(x => x.UpdatedAt)
            .ToList();
    }
}