// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Xunit;

namespace Microsoft.AspNetCore.Http.Extensions.Tests
{
    public class SendFileResponseExtensionsTests
    {
        [Fact]
        public Task SendFileWhenFileNotFoundThrows()
        {
            var response = new DefaultHttpContext().Response;
            return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
        }

        [Fact]
        public async Task SendFileWorks()
        {
            var context = new DefaultHttpContext();
            var response = context.Response;
            var fakeFeature = new FakeResponseBodyFeature();
            context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);

            await response.SendFileAsync("bob", 1, 3, CancellationToken.None);

            Assert.Equal("bob", fakeFeature.Name);
            Assert.Equal(1, fakeFeature.Offset);
            Assert.Equal(3, fakeFeature.Length);
            Assert.Equal(CancellationToken.None, fakeFeature.Token);
        }

        [Fact]
        public async Task SendFile_FallsBackToBodyStream()
        {
            var body = new MemoryStream();
            var context = new DefaultHttpContext();
            var response = context.Response;
            response.Body = body;

            await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);

            Assert.Equal(3, body.Length);
        }

        [Fact]
        public async Task SendFile_Stream_ThrowsWhenCanceled()
        {
            var body = new MemoryStream();
            var context = new DefaultHttpContext();
            var response = context.Response;
            response.Body = body;

            await Assert.ThrowsAnyAsync<OperationCanceledException>(
                () => response.SendFileAsync("testfile1kb.txt", 1, 3, new CancellationToken(canceled: true)));

            Assert.Equal(0, body.Length);
        }

        [Fact]
        public async Task SendFile_Feature_ThrowsWhenCanceled()
        {
            var context = new DefaultHttpContext();
            var fakeFeature = new FakeResponseBodyFeature();
            context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
            var response = context.Response;

            await Assert.ThrowsAsync<OperationCanceledException>(
                () => response.SendFileAsync("testfile1kb.txt", 1, 3, new CancellationToken(canceled: true)));
        }

        [Fact]
        public async Task SendFile_Stream_AbortsSilentlyWhenRequestCanceled()
        {
            var body = new MemoryStream();
            var context = new DefaultHttpContext();
            context.RequestAborted = new CancellationToken(canceled: true);
            var response = context.Response;
            response.Body = body;

            await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);

            Assert.Equal(0, body.Length);
        }

        [Fact]
        public async Task SendFile_Feature_AbortsSilentlyWhenRequestCanceled()
        {
            var context = new DefaultHttpContext();
            var fakeFeature = new FakeResponseBodyFeature();
            context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
            var token = new CancellationToken(canceled: true);
            context.RequestAborted = token;
            var response = context.Response;

            await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);

            Assert.Equal(token, fakeFeature.Token);
        }

        private class FakeResponseBodyFeature : IHttpResponseBodyFeature
        {
            public string Name { get; set; } = null;
            public long Offset { get; set; } = 0;
            public long? Length { get; set; } = null;
            public CancellationToken Token { get; set; }

            public Stream Stream => throw new System.NotImplementedException();

            public PipeWriter Writer => throw new System.NotImplementedException();

            public Task CompleteAsync()
            {
                throw new System.NotImplementedException();
            }

            public void DisableBuffering()
            {
                throw new System.NotImplementedException();
            }

            public Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
            {
                Name = path;
                Offset = offset;
                Length = length;
                Token = cancellation;

                cancellation.ThrowIfCancellationRequested();
                return Task.FromResult(0);
            }

            public Task StartAsync(CancellationToken token = default)
            {
                throw new System.NotImplementedException();
            }
        }
    }
}
