// <copyright file="AlertsTest.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>

using NUnit.Framework;
using OpenQA.Selenium.Environment;
using System;
using System.Collections.Generic;

namespace OpenQA.Selenium;

[TestFixture]
public class AlertsTest : DriverTestFixture
{
    [Test]
    public void ShouldBeAbleToOverrideTheWindowAlertMethod()
    {
        driver.Url = CreateAlertPage("cheese");

        ((IJavaScriptExecutor)driver).ExecuteScript(
            "window.alert = function(msg) { document.getElementById('text').innerHTML = msg; }");
        driver.FindElement(By.Id("alert")).Click();
    }

    [Test]
    public void ShouldAllowUsersToAcceptAnAlertManually()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Accept();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Alerts"));
    }

    [Test]
    public void ShouldThrowArgumentNullExceptionWhenKeysNull()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();
        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        try
        {
            Assert.That(
                () => alert.SendKeys(null),
                Throws.ArgumentNullException);
        }
        finally
        {
            alert.Accept();
        }
    }

    [Test]
    public void ShouldAllowUsersToAcceptAnAlertWithNoTextManually()
    {
        driver.Url = CreateAlertPage("");

        driver.FindElement(By.Id("alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Accept();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Alerts"));
    }

    [Test]
    public void ShouldAllowUsersToDismissAnAlertManually()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Dismiss();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Alerts"));
    }

    [Test]
    public void ShouldAllowAUserToAcceptAPrompt()
    {
        driver.Url = CreatePromptPage(null);

        driver.FindElement(By.Id("prompt")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Accept();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Prompt"));
    }

    [Test]
    public void ShouldAllowAUserToDismissAPrompt()
    {
        driver.Url = CreatePromptPage(null);

        driver.FindElement(By.Id("prompt")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Dismiss();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Prompt"));
    }

    [Test]
    public void ShouldAllowAUserToSetTheValueOfAPrompt()
    {
        driver.Url = CreatePromptPage(null);

        driver.FindElement(By.Id("prompt")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.SendKeys("cheese");
        alert.Accept();

        string result = driver.FindElement(By.Id("text")).Text;
        Assert.That(result, Is.EqualTo("cheese"));
    }

    [Test]
    public void SettingTheValueOfAnAlertThrows()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");

        try
        {
            Assert.That(
                () => alert.SendKeys("cheese"),
                Throws.TypeOf<ElementNotInteractableException>());
        }
        finally
        {
            alert.Accept();
        }
    }

    [Test]
    public void ShouldAllowTheUserToGetTheTextOfAnAlert()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        string value = alert.Text;
        alert.Accept();

        Assert.That(value, Is.EqualTo("cheese"));
    }

    [Test]
    public void ShouldAllowTheUserToGetTheTextOfAPrompt()
    {
        driver.Url = CreatePromptPage(null);

        driver.FindElement(By.Id("prompt")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        string value = alert.Text;
        alert.Accept();

        Assert.That(value, Is.EqualTo("Enter something"));
    }

    [Test]
    public void AlertShouldNotAllowAdditionalCommandsIfDismissed()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Dismiss();

        Assert.That(
            () => alert.Text,
            Throws.TypeOf<NoAlertPresentException>());
    }

    [Test]
    public void ShouldAllowUsersToAcceptAnAlertInAFrame()
    {
        string iframe = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithBody("<a href='#' id='alertInFrame' onclick='alert(\"framed cheese\");'>click me</a>"));
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithTitle("Testing Alerts")
            .WithBody(String.Format("<iframe src='{0}' name='iframeWithAlert'></iframe>", iframe)));

        driver.SwitchTo().Frame("iframeWithAlert");

        driver.FindElement(By.Id("alertInFrame")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Accept();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Alerts"));
    }

    [Test]
    public void ShouldAllowUsersToAcceptAnAlertInANestedFrame()
    {
        string iframe = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithBody("<a href='#' id='alertInFrame' onclick='alert(\"framed cheese\");'>click me</a>"));
        string iframe2 = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithBody(string.Format("<iframe src='{0}' name='iframeWithAlert'></iframe>", iframe)));
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithTitle("Testing Alerts")
            .WithBody(string.Format("<iframe src='{0}' name='iframeWithIframe'></iframe>", iframe2)));

        driver.SwitchTo().Frame("iframeWithIframe").SwitchTo().Frame("iframeWithAlert");

        driver.FindElement(By.Id("alertInFrame")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Accept();

        // If we can perform any action, we're good to go
        Assert.That(driver.Title, Is.EqualTo("Testing Alerts"));
    }

    [Test]
    public void SwitchingToMissingAlertThrows()
    {
        driver.Url = CreateAlertPage("cheese");

        Assert.That(
            () => AlertToBePresent(),
            Throws.TypeOf<NoAlertPresentException>());
    }

    [Test]
    [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
    public void SwitchingToMissingAlertInAClosedWindowThrows()
    {
        string blank = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage());
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithBody(String.Format(
                "<a id='open-new-window' href='{0}' target='newwindow'>open new window</a>", blank)));

        string mainWindow = driver.CurrentWindowHandle;
        try
        {
            driver.FindElement(By.Id("open-new-window")).Click();
            WaitFor(WindowHandleCountToBe(2), "Window count was not 2");
            WaitFor(WindowWithName("newwindow"), "Could not find window with name 'newwindow'");
            driver.Close();
            WaitFor(WindowHandleCountToBe(1), "Window count was not 1");

            Assert.That(
                () => AlertToBePresent().Accept(),
                Throws.TypeOf<NoSuchWindowException>());

        }
        finally
        {
            driver.SwitchTo().Window(mainWindow);
            WaitFor(ElementTextToEqual(driver.FindElement(By.Id("open-new-window")), "open new window"), "Could not find element with text 'open new window'");
        }
    }

    [Test]
    public void PromptShouldUseDefaultValueIfNoKeysSent()
    {
        driver.Url = CreatePromptPage("This is a default value");
        driver.FindElement(By.Id("prompt")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Accept();

        IWebElement element = driver.FindElement(By.Id("text"));
        WaitFor(ElementTextToEqual(element, "This is a default value"), "Element text was not 'This is a default value'");
        Assert.That(element.Text, Is.EqualTo("This is a default value"));
    }

    [Test]
    public void PromptShouldHaveNullValueIfDismissed()
    {
        driver.Url = CreatePromptPage("This is a default value");
        driver.FindElement(By.Id("prompt")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert.Dismiss();
        IWebElement element = driver.FindElement(By.Id("text"));
        WaitFor(ElementTextToEqual(element, "null"), "Element text was not 'null'");
        Assert.That(element.Text, Is.EqualTo("null"));
    }

    [Test]
    [IgnoreBrowser(Browser.Remote)]
    public void HandlesTwoAlertsFromOneInteraction()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithScripts(
                """
                function setInnerText(id, value) {
                  document.getElementById(id).innerHTML = '<p>' + value + '</p>';
                }

                function displayTwoPrompts() {
                  setInnerText('text1', prompt('First'));
                  setInnerText('text2', prompt('Second'));
                }
                """)
            .WithBody(
                """
                <a href='#' id='double-prompt' onclick='displayTwoPrompts();'>click me</a>
                <div id='text1'></div>
                <div id='text2'></div>
                """));

        driver.FindElement(By.Id("double-prompt")).Click();

        IAlert alert1 = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert1.SendKeys("brie");
        alert1.Accept();

        IAlert alert2 = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        alert2.SendKeys("cheddar");
        alert2.Accept();

        IWebElement element1 = driver.FindElement(By.Id("text1"));
        WaitFor(ElementTextToEqual(element1, "brie"), "Element text was not 'brie'");
        Assert.That(element1.Text, Is.EqualTo("brie"));
        IWebElement element2 = driver.FindElement(By.Id("text2"));
        WaitFor(ElementTextToEqual(element2, "cheddar"), "Element text was not 'cheddar'");
        Assert.That(element2.Text, Is.EqualTo("cheddar"));
    }

    [Test]
    public void ShouldHandleAlertOnPageLoad()
    {
        string pageWithOnLoad = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithOnLoad("""javascript:alert("onload")""")
            .WithBody("<p>Page with onload event handler</p>"));
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithBody(string.Format("<a id='open-page-with-onload-alert' href='{0}'>open new page</a>", pageWithOnLoad)));

        driver.FindElement(By.Id("open-page-with-onload-alert")).Click();

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        string value = alert.Text;
        alert.Accept();

        Assert.That(value, Is.EqualTo("onload"));
        IWebElement element = driver.FindElement(By.TagName("p"));
        WaitFor(ElementTextToEqual(element, "Page with onload event handler"), "Element text was not 'Page with onload event handler'");
    }

    [Test]

    public void ShouldHandleAlertOnPageLoadUsingGet()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithOnLoad("javascript:alert(\"onload\")")
            .WithBody("<p>Page with onload event handler</p>"));

        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        string value = alert.Text;
        alert.Accept();

        Assert.That(value, Is.EqualTo("onload"));
        WaitFor(ElementTextToEqual(driver.FindElement(By.TagName("p")), "Page with onload event handler"), "Could not find element with text 'Page with onload event handler'");
    }

    [Test]
    [IgnoreBrowser(Browser.IE, "Edge in IE Mode does not properly handle multiple windows")]
    [IgnoreBrowser(Browser.Chrome, "Test with onLoad alert hangs Chrome.")]
    [IgnoreBrowser(Browser.Edge, "Test with onLoad alert hangs Edge.")]
    [IgnoreBrowser(Browser.Safari, "Safari driver does not allow commands in any window when an alert is active")]
    public void ShouldNotHandleAlertInAnotherWindow()
    {
        string pageWithOnLoad = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithOnLoad("javascript:alert(\"onload\")")
            .WithBody("<p>Page with onload event handler</p>"));
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithBody(string.Format(
                "<a id='open-new-window' href='{0}' target='newwindow'>open new window</a>", pageWithOnLoad)));

        string mainWindow = driver.CurrentWindowHandle;
        string onloadWindow = null;
        try
        {
            driver.FindElement(By.Id("open-new-window")).Click();
            List<String> allWindows = new List<string>(driver.WindowHandles);
            allWindows.Remove(mainWindow);
            Assert.That(allWindows, Has.One.Items);
            onloadWindow = allWindows[0];

            Assert.That(() =>
            {
                IWebElement el = driver.FindElement(By.Id("open-new-window"));
                WaitFor<IAlert>(AlertToBePresent, TimeSpan.FromSeconds(5), "No alert found");
            },
            Throws.TypeOf<WebDriverException>());

        }
        finally
        {
            driver.SwitchTo().Window(onloadWindow);
            WaitFor<IAlert>(AlertToBePresent, "No alert found").Dismiss();
            driver.Close();
            driver.SwitchTo().Window(mainWindow);
            WaitFor(ElementTextToEqual(driver.FindElement(By.Id("open-new-window")), "open new window"), "Could not find element with text 'open new window'");
        }
    }

    [Test]
    [IgnoreBrowser(Browser.Firefox, "Driver chooses not to return text from unhandled alert")]
    public void IncludesAlertTextInUnhandledAlertException()
    {
        driver.Url = CreateAlertPage("cheese");

        driver.FindElement(By.Id("alert")).Click();
        WaitFor<IAlert>(AlertToBePresent, "No alert found");

        Assert.That(
            () => driver.Title,
            Throws.TypeOf<UnhandledAlertException>().With.Property(nameof(UnhandledAlertException.AlertText)).EqualTo("cheese"));
    }

    [Test]
    [NeedsFreshDriver(IsCreatedAfterTest = true)]
    public void CanQuitWhenAnAlertIsPresent()
    {
        driver.Url = CreateAlertPage("cheese");
        driver.FindElement(By.Id("alert")).Click();
        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        EnvironmentManager.Instance.CloseCurrentDriver();
    }

    [Test]
    public void ShouldHandleAlertOnFormSubmit()
    {
        driver.Url = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithTitle("Testing Alerts").
            WithBody("<form id='theForm' action='javascript:alert(\"Tasty cheese\");'>",
                "<input id='unused' type='submit' value='Submit'>",
                "</form>"));

        IWebElement element = driver.FindElement(By.Id("theForm"));
        element.Submit();
        IAlert alert = WaitFor<IAlert>(AlertToBePresent, "No alert found");
        string text = alert.Text;
        alert.Accept();

        Assert.That(text, Is.EqualTo("Tasty cheese"));
        Assert.That(driver.Title, Is.EqualTo("Testing Alerts"));
    }

    private IAlert AlertToBePresent()
    {
        return driver.SwitchTo().Alert();
    }

    private string CreateAlertPage(string alertText)
    {
        return EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithTitle("Testing Alerts")
            .WithBody("<a href='#' id='alert' onclick='alert(\"" + alertText + "\");'>click me</a>"));
    }

    private string CreatePromptPage(string defaultText)
    {
        return EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage()
            .WithTitle("Testing Prompt")
            .WithScripts(
                "function setInnerText(id, value) {",
                "  document.getElementById(id).innerHTML = '<p>' + value + '</p>';",
                "}",
                defaultText == null
                  ? "function displayPrompt() { setInnerText('text', prompt('Enter something')); }"
                  : "function displayPrompt() { setInnerText('text', prompt('Enter something', '" + defaultText + "')); }")

            .WithBody(
                "<a href='#' id='prompt' onclick='displayPrompt();'>click me</a>",
                "<div id='text'>acceptor</div>"));
    }

    private void SetSimpleOnBeforeUnload(string returnText)
    {
        ((IJavaScriptExecutor)driver).ExecuteScript(
            "var returnText = arguments[0]; window.onbeforeunload = function() { return returnText; }",
            returnText);
    }

    private Func<IWebElement> ElementToBePresent(By locator)
    {
        return () =>
        {
            try
            {
                return driver.FindElement(By.Id("open-page-with-onunload-alert"));
            }
            catch (NoSuchElementException)
            {
                return null;
            }
        };
    }

    private Func<bool> ElementTextToEqual(IWebElement element, string text)
    {
        return () =>
        {
            return element.Text == text;
        };
    }

    private Func<bool> WindowWithName(string name)
    {
        return () =>
        {
            try
            {
                driver.SwitchTo().Window(name);
                return true;
            }
            catch (NoSuchWindowException)
            {
                return false;
            }
        };
    }

    private Func<bool> WindowHandleCountToBe(int count)
    {
        return () =>
        {
            return driver.WindowHandles.Count == count;
        };
    }

}
