/*
 * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET)
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

using System;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter;
using SafeExamBrowser.Settings.Browser.Proxy;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;

namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
	[TestClass]
	public class RequestHandlerTests
	{
		private AppConfig appConfig;
		private Mock<IRequestFilter> filter;
		private Mock<IKeyGenerator> keyGenerator;
		private Mock<ILogger> logger;
		private BrowserSettings settings;
		private WindowSettings windowSettings;
		private ResourceHandler resourceHandler;
		private Mock<IText> text;
		private TestableRequestHandler sut;

		[TestInitialize]
		public void Initialize()
		{
			appConfig = new AppConfig();
			filter = new Mock<IRequestFilter>();
			keyGenerator = new Mock<IKeyGenerator>();
			logger = new Mock<ILogger>();
			settings = new BrowserSettings();
			windowSettings = new WindowSettings();
			text = new Mock<IText>();
			resourceHandler = new ResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, default, settings, windowSettings, text.Object);

			sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings);
		}

		[TestMethod]
		public void MustBlockSpecialWindowDispositions()
		{
			Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewBackgroundTab, default));
			Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewPopup, default));
			Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewWindow, default));
			Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.SaveToDisk, default));
		}

		[TestMethod]
		public void MustDetectQuitUrl()
		{
			var eventFired = false;
			var quitUrl = "http://www.byebye.com";
			var request = new Mock<IRequest>();

			appConfig.ConfigurationFileMimeType = "application/seb";
			request.SetupGet(r => r.Url).Returns(quitUrl);
			settings.QuitUrl = quitUrl;
			sut.QuitUrlVisited += (url) => eventFired = true;

			var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			Assert.IsTrue(blocked);
			Assert.IsTrue(eventFired);

			blocked = false;
			eventFired = false;
			request.SetupGet(r => r.Url).Returns("http://www.bye.com");

			blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			Assert.IsFalse(blocked);
			Assert.IsFalse(eventFired);
		}

		[TestMethod]
		public void MustIgnoreTrailingSlashForQuitUrl()
		{
			var eventFired = false;
			var quitUrl = "http://www.byebye.com";
			var request = new Mock<IRequest>();

			request.SetupGet(r => r.Url).Returns($"{quitUrl}/");
			settings.QuitUrl = quitUrl;
			sut.QuitUrlVisited += (url) => eventFired = true;

			var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			Assert.IsTrue(blocked);
			Assert.IsTrue(eventFired);

			blocked = false;
			eventFired = false;
			request.SetupGet(r => r.Url).Returns(quitUrl);
			settings.QuitUrl = $"{quitUrl}/";

			blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			Assert.IsTrue(blocked);
			Assert.IsTrue(eventFired);
		}

		[TestMethod]
		public void MustFilterMainRequests()
		{
			var eventFired = false;
			var request = new Mock<IRequest>();
			var url = "https://www.test.org";

			appConfig.ConfigurationFileMimeType = "application/seb";
			filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
			request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
			request.SetupGet(r => r.Url).Returns(url);
			settings.Filter.ProcessContentRequests = false;
			settings.Filter.ProcessMainRequests = true;
			sut.RequestBlocked += (u) => eventFired = true;

			var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);

			Assert.IsTrue(blocked);
			Assert.IsTrue(eventFired);

			blocked = false;
			eventFired = false;
			request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);

			blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);

			Assert.IsFalse(blocked);
			Assert.IsFalse(eventFired);
		}

		[TestMethod]
		public void MustFilterContentRequests()
		{
			var eventFired = false;
			var request = new Mock<IRequest>();
			var url = "https://www.test.org";

			appConfig.ConfigurationFileMimeType = "application/seb";
			filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
			request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
			request.SetupGet(r => r.Url).Returns(url);
			settings.Filter.ProcessContentRequests = true;
			settings.Filter.ProcessMainRequests = false;
			sut.RequestBlocked += (u) => eventFired = true;

			var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);

			Assert.IsTrue(blocked);
			Assert.IsFalse(eventFired);

			blocked = false;
			eventFired = false;
			request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);

			blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);

			filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);

			Assert.IsFalse(blocked);
			Assert.IsFalse(eventFired);
		}

		[TestMethod]
		public void MustInitiateDataUriConfigurationFileDownload()
		{
			var browser = new Mock<IBrowser>();
			var host = new Mock<IBrowserHost>();
			var request = new Mock<IRequest>();

			appConfig.ConfigurationFileExtension = ".xyz";
			appConfig.ConfigurationFileMimeType = "application/seb";
			appConfig.SebUriScheme = "abc";
			appConfig.SebUriSchemeSecure = "abcd";
			browser.Setup(b => b.GetHost()).Returns(host.Object);
			request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...");

			var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);

			host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"data:{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...")));
			Assert.IsTrue(handled);
		}

		[TestMethod]
		public void MustInitiateHttpConfigurationFileDownload()
		{
			var browser = new Mock<IBrowser>();
			var host = new Mock<IBrowserHost>();
			var request = new Mock<IRequest>();

			appConfig.ConfigurationFileExtension = ".xyz";
			appConfig.ConfigurationFileMimeType = "application/seb";
			appConfig.SebUriScheme = "abc";
			appConfig.SebUriSchemeSecure = "abcd";
			browser.Setup(b => b.GetHost()).Returns(host.Object);
			request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriScheme}://host.com/path/file{appConfig.ConfigurationFileExtension}");

			var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);

			host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttp}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
			Assert.IsTrue(handled);
		}

		[TestMethod]
		public void MustInitiateHttpsConfigurationFileDownload()
		{
			var browser = new Mock<IBrowser>();
			var host = new Mock<IBrowserHost>();
			var request = new Mock<IRequest>();

			appConfig.ConfigurationFileExtension = ".xyz";
			appConfig.ConfigurationFileMimeType = "application/seb";
			appConfig.SebUriScheme = "abc";
			appConfig.SebUriSchemeSecure = "abcd";
			browser.Setup(b => b.GetHost()).Returns(host.Object);
			request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://host.com/path/file{appConfig.ConfigurationFileExtension}");

			var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);

			host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttps}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
			Assert.IsTrue(handled);
		}

		[TestMethod]
		public void MustReturnResourceHandler()
		{
			var disableDefaultHandling = default(bool);
			var handler = sut.GetResourceRequestHandler(default, default, default, default, default, default, default, ref disableDefaultHandling);

			Assert.AreSame(resourceHandler, handler);
		}

		[TestMethod]
		public void MustUseProxyCredentials()
		{
			var callback = new Mock<IAuthCallback>();
			var proxy1 = new ProxyConfiguration { Host = "www.test.com", Username = "Sepp", Password = "1234", Port = 10, RequiresAuthentication = true };
			var proxy2 = new ProxyConfiguration { Host = "www.nope.com", Username = "Peter", Password = "4321", Port = 10, RequiresAuthentication = false };

			settings.Proxy.Proxies.Add(proxy1);
			settings.Proxy.Proxies.Add(proxy2);

			var result = sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, true, "WWW.tEst.Com", 10, default, default, callback.Object);

			callback.Verify(c => c.Cancel(), Times.Never);
			callback.Verify(c => c.Continue(It.Is<string>(u => u.Equals(proxy1.Username)), It.Is<string>(p => p.Equals(proxy1.Password))), Times.Once);
			callback.Verify(c => c.Continue(It.Is<string>(u => u.Equals(proxy2.Username)), It.Is<string>(p => p.Equals(proxy2.Password))), Times.Never);

			Assert.IsTrue(result);
		}

		[TestMethod]
		public void MustNotUseProxyCredentialsIfNoProxy()
		{
			var callback = new Mock<IAuthCallback>();

			sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, false, default, default, default, default, callback.Object);

			callback.Verify(c => c.Cancel(), Times.Never);
			callback.Verify(c => c.Continue(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
		}

		private class TestableRequestHandler : RequestHandler
		{
			internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, ResourceHandler resourceHandler, BrowserSettings settings, WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
			{
			}

			public new bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
			{
				return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
			}

			public new IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
			{
				return base.GetResourceRequestHandler(webBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling);
			}

			public new bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
			{
				return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
			}

			public new bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
			{
				return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
			}
		}
	}
}