// Copyright (c) Barry Dorrans. All rights reserved.
// Licensed under the MIT License.

using idunno.AtProto;
using idunno.AtProto.Authentication;
using idunno.Bluesky.Embed;
using idunno.Bluesky.Video;

namespace idunno.Bluesky;

public partial class BlueskyAgent
{
    private readonly Uri _videoServer = new("https://video.bsky.app/");

    private const string UploadBlobLxm = "com.atproto.repo.uploadBlob";

    /// <summary>
    /// Gets the status details for the specified video processing job.
    /// </summary>
    /// <param name="jobId">The job id whose status should be queried.</param>
    /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
    /// <returns>The task object representing the asynchronous operation.</returns>
    /// <exception cref="ArgumentException">Thrown when <paramref name="jobId"/> is <see langword="null"/> or whitespace.</exception>
    public async Task<AtProtoHttpResult<JobStatus>> GetVideoJobStatus(
        string jobId,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(jobId);

        using (_logger.BeginScope($"Getting jobStatus for {jobId}"))
        {
            AtProtoHttpResult<JobStatus> result = await BlueskyServer.GetVideoJobStatus(
                jobId,
                _videoServer,
                HttpClient,
                LoggerFactory,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            if (result.Succeeded)
            {
                Logger.GetJobStatusSucceeded(_logger, jobId, result.Result.State, result.Result.Progress);
            }
            else
            {
                string? error = null;
                string? message = null;
                if (result.AtErrorDetail is not null)
                {
                    error = result.AtErrorDetail.Error;
                    message = result.AtErrorDetail.Message;
                }

                Logger.GetJobStatusFailed(_logger, result.StatusCode, error, message);
            }

            return result;
        }
    }

    /// <summary>
    /// Gets any video upload restrictions placed on the current user 
    /// </summary>
    /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
    /// <returns>The task object representing the asynchronous operation.</returns>
    /// <exception cref="AuthenticationRequiredException">Thrown when the agent is not authenticated.</exception>
    public async Task<AtProtoHttpResult<UploadLimits>> GetVideoUploadLimits(CancellationToken cancellationToken = default)
    {
        if (!IsAuthenticated)
        {
            throw new AuthenticationRequiredException();
        }

        using (_logger.BeginScope($"Getting video upload limits for {Did}"))
        {
            AtProtoHttpResult<ServiceCredential> getServiceAuthResult = await GetServiceAuth(
                service: Service,
                audience: WellKnownDistributedIdentifiers.Video,
                lxm: "app.bsky.video.getUploadLimits",
                cancellationToken: cancellationToken).ConfigureAwait(false);

            if (!getServiceAuthResult.Succeeded)
            {
                return new AtProtoHttpResult<UploadLimits>(
                    null,
                    getServiceAuthResult.StatusCode,
                    getServiceAuthResult.HttpResponseHeaders,
                    getServiceAuthResult.AtErrorDetail,
                    getServiceAuthResult.RateLimit);
            }

            AtProtoHttpResult<UploadLimits> result = await BlueskyServer.GetVideoUploadStatus(
                _videoServer,
                serviceCredential: getServiceAuthResult.Result,
                httpClient: HttpClient,
                loggerFactory: LoggerFactory,
                cancellationToken: cancellationToken).ConfigureAwait(false);

            if (result.Succeeded)
            {
                Logger.GetUploadLimitsSucceeded(_logger, Did, result.Result.CanUpload, result.Result.RemainingDailyVideos, result.Result.RemainingDailyBytes);
            }
            else
            {
                string? error = null;
                string? message = null;
                if (result.AtErrorDetail is not null)
                {
                    error = result.AtErrorDetail.Error;
                    message = result.AtErrorDetail.Message;
                }

                Logger.GetUploadLimitsFailed(_logger, result.StatusCode, Did, error, message);
            }

            return result;
        }
    }

    /// <summary>
    /// Uploads an caption file to be referenced in an embedded video.
    /// </summary>
    /// <param name="captionsAsBytes">The captions, as a byte array.</param>
    /// <param name="captionLanguage">The language the captions are in.</param>
    /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
    /// <returns>The task object representing the asynchronous operation.</returns>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="captionsAsBytes"/> is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentException">Thrown when <paramref name="captionLanguage"/> is <see langword="null"/> or empty.</exception>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="captionsAsBytes"/> is a zero length array.</exception>
    /// <exception cref="AuthenticationRequiredException">Thrown when the current session is not an authenticated session.</exception>
    public async Task<AtProtoHttpResult<Caption>> UploadCaptions(
        byte[] captionsAsBytes,
        string captionLanguage,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(captionsAsBytes);
        ArgumentException.ThrowIfNullOrEmpty(captionLanguage);
        ArgumentOutOfRangeException.ThrowIfZero(captionsAsBytes.Length);

        if (!IsAuthenticated)
        {
            throw new AuthenticationRequiredException();
        }

        AtProtoHttpResult<Blob> uploadResult = await UploadBlob(
            captionsAsBytes,
            "text/vtt",
            cancellationToken: cancellationToken).ConfigureAwait(false);


        if (uploadResult.Succeeded)
        {
            return new AtProtoHttpResult<Caption>(
                new Caption(captionLanguage, uploadResult.Result),
                uploadResult.StatusCode,
                uploadResult.HttpResponseHeaders,
                uploadResult.AtErrorDetail,
                uploadResult.RateLimit);
        }
        else
        {
            return new AtProtoHttpResult<Caption>(
                null,
                uploadResult.StatusCode,
                uploadResult.HttpResponseHeaders,
                uploadResult.AtErrorDetail,
                uploadResult.RateLimit);
        }
    }
}