// Copyright (c) Microsoft Corporation
// SPDX-License-Identifier: MIT
using System.Net;
using System.Xml;

namespace GuestProxyAgentTest.Utilities
{
    /// <summary>
    /// JUnit test result builder
    /// </summary>
    public class JunitTestResultBuilder
    {
        // key is test suit name, value is junit-doc, junit-suite
        // azure public publish test result task doesn't display result correctly, if result has multiple test suites
        // work around is create one result file per test suite (test scenario in E2E)
        private readonly Dictionary<string, (XmlDocument, XmlElement)> testSuiteMap = new Dictionary<string, (XmlDocument, XmlElement)>();
        private readonly string testGroupName = null!;
        private readonly string testResultGroupFolder = null!;
        internal string testResultFolder = null!;

        public JunitTestResultBuilder(string testResultFolder, string testGroupName)
        {
            this.testGroupName = testGroupName;
            this.testResultGroupFolder = Path.Combine(testResultFolder, testGroupName);
            Directory.CreateDirectory(this.testResultGroupFolder);
            this.testResultFolder = testResultFolder;
        }

        /// <summary>
        /// Add success test result, it will merge stdoutput with customoutput.
        /// </summary>
        /// <param name="testScenarioName"></param>
        /// <param name="testName"></param>
        /// <param name="stdOut"></param>
        /// <param name="customOut"></param>
        /// <returns></returns>
        public JunitTestResultBuilder AddSuccessTestResult(string testScenarioName, string testName, string stdOut, string customOut, long durationInMilliseconds = 0)
        {
            var stdOutMessage = "Std output:"
                + Environment.NewLine
                + stdOut
                + Environment.NewLine
                + "Custom output:"
                + Environment.NewLine
                + customOut;
            return AddSuccessTestResult(testScenarioName, testName, stdOutMessage, durationInMilliseconds);
        }

        /// <summary>
        /// Add succeed test result
        /// </summary>
        /// <param name="testScenarioName"></param>
        /// <param name="testName"></param>
        /// <param name="stdOutMessage"></param>
        /// <returns></returns>
        public JunitTestResultBuilder AddSuccessTestResult(string testScenarioName, string testName, string stdOutMessage, long durationInMilliseconds = 0)
        {
            lock (this)
            {
                XmlDocument doc = null!;
                if (!testSuiteMap.ContainsKey(testScenarioName))
                {
                    doc = new XmlDocument();
                    var testsuites = doc.CreateElement("testsuites");
                    doc.AppendChild(testsuites);

                    XmlElement testSuiteElement = doc.CreateElement("testsuite");
                    testSuiteElement.SetAttribute("name", testGroupName + "." + testScenarioName);
                    testSuiteElement.SetAttribute("tests", "0");
                    testSuiteElement.SetAttribute("errors", "0");
                    testSuiteElement.SetAttribute("failures", "0");
                    testSuiteElement.SetAttribute("skipped", "0");
                    testSuiteElement.SetAttribute("timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss"));
                    testsuites.AppendChild(testSuiteElement);
                    testSuiteMap[testScenarioName] = (doc, testSuiteElement);
                }
                doc = testSuiteMap[testScenarioName].Item1;
                var testSuite = testSuiteMap[testScenarioName].Item2;
                testSuite.SetAttribute("tests", StringAdd(testSuite.GetAttribute("tests"), 1));

                XmlElement successTestCaseElement = doc.CreateElement("testcase");
                successTestCaseElement.SetAttribute("name", testName);
                successTestCaseElement.SetAttribute("classname", testGroupName + "." + testScenarioName);
                successTestCaseElement.SetAttribute("time", ((double)durationInMilliseconds / 1000).ToString());
                testSuite.AppendChild(successTestCaseElement);

                XmlElement systemOutElement = doc.CreateElement("system-out");
                systemOutElement.InnerText = stdOutMessage;
                successTestCaseElement.AppendChild(systemOutElement);
            }

            return this;
        }

        /// <summary>
        /// Add failure test result
        /// </summary>
        /// <param name="testScenarioName"></param>
        /// <param name="testName"></param>
        /// <param name="stdOutMessage"></param>
        /// <param name="stdErrMessage"></param>
        /// <param name="customOutput"></param>
        /// <returns></returns>
        public JunitTestResultBuilder AddFailureTestResult(string testScenarioName, string testName, string stdOutMessage, string stdErrMessage, string customOutput, long durationInMilliseconds = 0)
        {
            lock (this)
            {
                XmlDocument doc = null!;
                if (!testSuiteMap.ContainsKey(testScenarioName))
                {
                    doc = new XmlDocument();
                    var testsuites = doc.CreateElement("testsuites");
                    doc.AppendChild(testsuites);

                    XmlElement testSuiteElement = doc.CreateElement("testsuite");
                    testSuiteElement.SetAttribute("name", testGroupName + "." + testScenarioName);
                    testSuiteElement.SetAttribute("tests", "0");
                    testSuiteElement.SetAttribute("errors", "0");
                    testSuiteElement.SetAttribute("failures", "0");
                    testSuiteElement.SetAttribute("skipped", "0");
                    testSuiteElement.SetAttribute("timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss"));
                    testsuites.AppendChild(testSuiteElement);
                    testSuiteMap[testScenarioName] = (doc, testSuiteElement);
                }
                doc = testSuiteMap[testScenarioName].Item1;
                var testSuite = testSuiteMap[testScenarioName].Item2;
                testSuite.SetAttribute("tests", StringAdd(testSuite.GetAttribute("tests"), 1));
                testSuite.SetAttribute("failures", StringAdd(testSuite.GetAttribute("failures"), 1));

                XmlElement failedTestCaseElement = doc.CreateElement("testcase");
                failedTestCaseElement.SetAttribute("name", testName);
                failedTestCaseElement.SetAttribute("classname", testGroupName + "." + testScenarioName);
                failedTestCaseElement.SetAttribute("time", ((double)durationInMilliseconds / 1000).ToString());
                testSuite.AppendChild(failedTestCaseElement);

                XmlElement systemOutElement = doc.CreateElement("system-out");
                systemOutElement.InnerText = stdOutMessage;
                failedTestCaseElement.AppendChild(systemOutElement);

                XmlElement systemErrElement = doc.CreateElement("system-err");
                systemErrElement.InnerText = stdErrMessage;
                failedTestCaseElement.AppendChild(systemErrElement);

                XmlElement failureElement = doc.CreateElement("failure");
                failureElement.SetAttribute("message", "Std Error Output: "
                    + Environment.NewLine
                    + stdErrMessage
                    + Environment.NewLine
                    + Environment.NewLine
                    + "Custom output: "
                    + Environment.NewLine
                    + customOutput);
                failureElement.SetAttribute("type", "AssertionException");
                failedTestCaseElement.AppendChild(failureElement);
            }
            return this;
        }

        public JunitTestResultBuilder AddAbortedTestResult(string testScenarioName, string testName, string message)
        {
            lock (this)
            {
                XmlDocument doc = null!;
                if (!testSuiteMap.ContainsKey(testScenarioName))
                {
                    doc = new XmlDocument();
                    var testsuites = doc.CreateElement("testsuites");
                    doc.AppendChild(testsuites);
                    XmlElement testSuiteElement = doc.CreateElement("testsuite");
                    testSuiteElement.SetAttribute("name", testGroupName + "." + testScenarioName);
                    testSuiteElement.SetAttribute("tests", "0");
                    testSuiteElement.SetAttribute("errors", "0");
                    testSuiteElement.SetAttribute("failures", "0");
                    testSuiteElement.SetAttribute("skipped", "0");
                    testSuiteElement.SetAttribute("timestamp", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss"));
                    testsuites.AppendChild(testSuiteElement);
                    testSuiteMap[testScenarioName] = (doc, testSuiteElement);
                }
                doc = testSuiteMap[testScenarioName].Item1;
                var testSuite = testSuiteMap[testScenarioName].Item2;
                testSuite.SetAttribute("tests", StringAdd(testSuite.GetAttribute("tests"), 1));
                testSuite.SetAttribute("skipped", StringAdd(testSuite.GetAttribute("skipped"), 1));
                XmlElement abortedTestCaseElement = doc.CreateElement("testcase");
                abortedTestCaseElement.SetAttribute("name", testName);
                abortedTestCaseElement.SetAttribute("classname", testGroupName + "." + testScenarioName);
                abortedTestCaseElement.SetAttribute("time", "0");
                testSuite.AppendChild(abortedTestCaseElement);
                XmlElement abortedElement = doc.CreateElement("skipped");
                abortedElement.SetAttribute("message", "Test case aborted.");
                abortedElement.InnerText = message;
                abortedTestCaseElement.AppendChild(abortedElement);
            }
            return this;
        }

        /// <summary>
        /// build and save the test result to file
        /// </summary>
        /// <returns></returns>
        public List<string> Build()
        {
            List<string> result = new List<string>();
            foreach (KeyValuePair<string, (XmlDocument, XmlElement)> kv in testSuiteMap)
            {
                var doc = kv.Value.Item1;
                var resultPath = Path.Combine(this.testResultGroupFolder, kv.Key + "-TestResults.xml");
                result.Add(resultPath);
                doc.Save(resultPath);
            }
            return result;
        }

        private string StringAdd(string str, int added)
        {
            var val = int.Parse(str) + added;
            return val + "";
        }

    }
}
