//-----------------------------------------------------------------------------
// <copyright file="JsonSegmentMarshallerTest.cs" company="Amazon.com">
//      Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
//      Licensed under the Apache License, Version 2.0 (the "License").
//      You may not use this file except in compliance with the License.
//      A copy of the License is located at
//
//      http://aws.amazon.com/apache2.0
//
//      or in the "license" file accompanying this file. This file 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 System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.XRay.Recorder.Core.Internal.Emitters;
using Amazon.XRay.Recorder.Core.Internal.Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ThirdParty.LitJson;

namespace Amazon.XRay.Recorder.UnitTests
{
    [TestClass]
    public class JsonSegmentMarshallerTest
    {
        private JsonSegmentMarshaller _marshaller;

        [TestInitialize]
        public void Initialize()
        {
            _marshaller = new JsonSegmentMarshaller();
        }

        [TestMethod]
        public void TestMarshallHttpMethod()
        {
            var segment = new Segment("test", "1-11111111-111111111111111111111111");
            segment.Id = "1111111111111111";
            segment.StartTime = 1;
            segment.EndTime = 2;
            
            // ensure method can be marshalled
            segment.Http["method"] = HttpMethod.Post;
            
            
            _marshaller.Marshall(segment);

            var expect = "{\"format\":\"json\",\"version\":1}\n{\"trace_id\":\"1-11111111-111111111111111111111111\",\"id\":\"1111111111111111\",\"start_time\":1,\"end_time\":2,\"name\":\"test\",\"http\":{\"method\":\"POST\"}}";
            var actual = _marshaller.Marshall(segment);
            
            Assert.AreEqual(expect, actual);
        }
        
        [TestMethod]
        public void TestMarshallConstantClass()
        {
            var segment = new Segment("test", "1-11111111-111111111111111111111111");
            segment.Id = "1111111111111111";
            segment.StartTime = 1;
            segment.EndTime = 2;
            
            // Some random instance of ConstantClass. Their constructor is private so we cant construct one
            segment.Aws["value"] = ReturnConsumedCapacity.INDEXES;
            
            
            _marshaller.Marshall(segment);

            var expect = "{\"format\":\"json\",\"version\":1}\n{\"trace_id\":\"1-11111111-111111111111111111111111\",\"id\":\"1111111111111111\",\"start_time\":1,\"end_time\":2,\"name\":\"test\",\"aws\":{\"value\":\"INDEXES\"}}";
            var actual = _marshaller.Marshall(segment);
            
            Assert.AreEqual(expect, actual);
        }

        [TestMethod]
        public void TestMarshallDelegate()
        {
            var segment = new Segment("test", "1-11111111-111111111111111111111111");
            segment.Id = "1111111111111111";
            segment.StartTime = 1;
            segment.EndTime = 2;

            Func<object, object> func = JsonSegmentMarshallerTest.SyncFunction;
            Action<object> action = JsonSegmentMarshallerTest.SyncAction;
            Func<object, Task<object>> asyncFunc = JsonSegmentMarshallerTest.AsyncFunction;

            var functionName = "function";
            var actionName = "action";
            var asyncFunctionName = "asyncFunction";

            segment.AddMetadata(functionName, func);
            segment.AddMetadata(actionName, action);
            segment.AddMetadata(asyncFunctionName, asyncFunc);

            var actual = _marshaller.Marshall(segment);
            var actualJson = JsonMapper.ToObject(actual.Split('\n')[1]);

            Assert.AreEqual("JsonSegmentMarshallerTest.SyncFunction(System.Func`2[System.Object,System.Object])",
                            (string)actualJson["metadata"]["default"][functionName]);
            Assert.AreEqual("JsonSegmentMarshallerTest.SyncAction(System.Action`1[System.Object])",
                            (string)actualJson["metadata"]["default"][actionName]);
            Assert.AreEqual("JsonSegmentMarshallerTest.AsyncFunction(System.Func`2[System.Object,System.Threading.Tasks.Task`1[System.Object]])",
                            (string)actualJson["metadata"]["default"][asyncFunctionName]);
        }

        public static object SyncFunction(object obj) => obj;
        public static void SyncAction(object obj) { return; }
        public static Task<object> AsyncFunction(object obj) => Task.FromResult(obj);

        [TestMethod]
        public void TestMarshallSimpleSegment()
        {
            var segment = new Segment("test", "1-11111111-111111111111111111111111");
            segment.Id = "1111111111111111";
            segment.StartTime = 100;
            segment.EndTime = 200;

            var expect = "{\"format\":\"json\",\"version\":1}\n{\"trace_id\":\"1-11111111-111111111111111111111111\",\"id\":\"1111111111111111\",\"start_time\":100,\"end_time\":200,\"name\":\"test\"}";
            var actual = _marshaller.Marshall(segment);

            Assert.AreEqual(expect, actual);
        }

        [TestMethod]
        public void TestMarshallSubsegment()
        {
            var parent = new Segment("parent", "1-11111111-111111111111111111111111");
            parent.Id = "1111111111111111";
            parent.StartTime = 100;
            parent.EndTime = 400;

            var child = new Subsegment("child");
            child.Id = "2222222222222222";
            child.StartTime = 200;
            child.EndTime = 300;

            parent.AddSubsegment(child);

            var expect = "{\"format\":\"json\",\"version\":1}\n{\"trace_id\":\"1-11111111-111111111111111111111111\",\"id\":\"1111111111111111\",\"start_time\":100,\"end_time\":400,\"name\":\"parent\",\"subsegments\":[{\"id\":\"2222222222222222\",\"start_time\":200,\"end_time\":300,\"name\":\"child\"}]}";
            var actual = _marshaller.Marshall(parent);

            Assert.AreEqual(actual, expect);
        }

        [TestMethod]
        public void TestMarshallSegmentWithAws()
        {
            var segment = new Segment("test", "1-11111111-111111111111111111111111");
            segment.Id = "1111111111111111";
            segment.StartTime = 100;
            segment.EndTime = 200;

            segment.Aws["region"] = "us-east-1";
            segment.Aws["operation"] = "ListTablesRequest";
            segment.Aws["request_id"] = "123456";

            var actual = _marshaller.Marshall(segment);
            var actualJson = JsonMapper.ToObject(actual.Split('\n')[1]);

            Assert.AreEqual("us-east-1", (string)actualJson["aws"]["region"]);
            Assert.AreEqual("ListTablesRequest", (string)actualJson["aws"]["operation"]);
            Assert.AreEqual("123456", (string)actualJson["aws"]["request_id"]);
        }

        [TestMethod]
        public void TestMarshallAddException()
        {
            try
            {
                throw new ArgumentNullException("value");
            }
            catch (ArgumentNullException e)
            {
                var subsegment = new Subsegment("test");
                subsegment.Id = "1111111111111111";
                subsegment.AddException(e);

                var actual = _marshaller.Marshall(subsegment);

                var trace = new StackTrace(true);
                var filePath = trace.GetFrame(0).GetFileName().Replace("\\", "\\\\");
                var line = new StackTrace(e, true).GetFrame(0).GetFileLineNumber();
                var workingDirectory = Directory.GetCurrentDirectory().Replace("\\", "\\\\");

#if NETCOREAPP3_1_OR_GREATER
                var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"fault\":true,\"cause\":{\"working_directory\":\"" + workingDirectory + "\",\"exceptions\":[{\"id\":\"" + subsegment.Cause.ExceptionDescriptors[0].Id + "\",\"message\":\"Value cannot be null. (Parameter 'value')\",\"type\":\"ArgumentNullException\",\"remote\":false,\"stack\":[{\"path\":\"" + filePath + "\",\"line\":" + line + ",\"label\":\"Amazon.XRay.Recorder.UnitTests.JsonSegmentMarshallerTest.TestMarshallAddException\"}]}]}}";
#else
                var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"fault\":true,\"cause\":{\"working_directory\":\"" + workingDirectory + "\",\"exceptions\":[{\"id\":\"" + subsegment.Cause.ExceptionDescriptors[0].Id + "\",\"message\":\"Value cannot be null." + Environment.NewLine.Replace("\r", @"\r").Replace("\n", @"\n") + "Parameter name: value\",\"type\":\"ArgumentNullException\",\"remote\":false,\"stack\":[{\"path\":\"" + filePath + "\",\"line\":" + line + ",\"label\":\"Amazon.XRay.Recorder.UnitTests.JsonSegmentMarshallerTest.TestMarshallAddException\"}]}]}}";
#endif

                Assert.AreEqual(expected, actual);
            }
        }

        [TestMethod]
        public void TestMarshallError()
        {
            try
            {
                throw new ArgumentNullException("value");
            }
            catch (ArgumentNullException)
            {
                var subsegment = new Subsegment("test");
                subsegment.Id = "1111111111111111";
                subsegment.HasError = true;

                var actual = _marshaller.Marshall(subsegment);
                var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"error\":true}";

                Assert.AreEqual(expected, actual);
            }
        }

        [TestMethod]
        public void TestMarshallAnnotation()
        {
            var subsegment = new Subsegment("test");
            subsegment.Id = "1111111111111111";
            subsegment.AddAnnotation("string", "US");
            subsegment.AddAnnotation("bool", true);
            subsegment.AddAnnotation("int", 98177);
            subsegment.AddAnnotation("long", 123456789123456L);
            subsegment.AddAnnotation("double", 100.256);

            var actual = _marshaller.Marshall(subsegment);
            var actualJson = JsonMapper.ToObject(actual.Split('\n')[1]);

            Assert.AreEqual("US", (string)actualJson["annotations"]["string"]);
            Assert.AreEqual(true, (bool)actualJson["annotations"]["bool"]);
            Assert.AreEqual(98177, (int)actualJson["annotations"]["int"]);
            Assert.AreEqual(123456789123456L, (long)actualJson["annotations"]["long"]);
            Assert.AreEqual(100.256, (double)actualJson["annotations"]["double"]);
        }

        [TestMethod]
        public void TestMarshallHttp()
        {
            var subsegment = new Subsegment("test");
            subsegment.Id = "1111111111111111";
            var request = new Dictionary<string, string>();
            request["url"] = @"http://hello-1.mbfzqxzcpe.us-east-1.elasticbeanstalk.com/foo";
            request["method"] = "GET";

            subsegment.Http["request"] = request;

            var actual = _marshaller.Marshall(subsegment);
            var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"http\":{\"request\":{\"url\":\"http://hello-1.mbfzqxzcpe.us-east-1.elasticbeanstalk.com/foo\",\"method\":\"GET\"}}}";

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void TestMarshallNamespace()
        {
            var subsegment = new Subsegment("test");
            subsegment.Id = "1111111111111111";
            subsegment.Namespace = "remote";

            var actual = _marshaller.Marshall(subsegment);
            var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"namespace\":\"remote\"}";

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void TestMarshallThrottle()
        {
            var subsegment = new Subsegment("test");
            subsegment.Id = "1111111111111111";
            subsegment.IsThrottled = true;

            var actual = _marshaller.Marshall(subsegment);
            var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"throttle\":true}";

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void TestMarshallPrecursorIds()
        {
            var subsegment = new Subsegment("test");
            subsegment.Id = "1111111111111111";
            subsegment.AddPrecursorId("2222222222222222");
            subsegment.AddPrecursorId("2222222222222222");
            subsegment.AddPrecursorId("3333333333333333");

            var actual = _marshaller.Marshall(subsegment);
            var expected = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"precursor_ids\":[\"2222222222222222\",\"3333333333333333\"]}";

            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void TestMarshallSql()
        {
            var subsegment = new Subsegment("test");
            subsegment.Id = "1111111111111111";
            subsegment.Sql["key1"] = "value1";
            subsegment.Sql["key2"] = "value2";

            var actual = _marshaller.Marshall(subsegment);
            var expected1 = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"sql\":{\"key2\":\"value2\",\"key1\":\"value1\"}}";
            var expected2 = "{\"format\":\"json\",\"version\":1}\n{\"id\":\"1111111111111111\",\"start_time\":0,\"end_time\":0,\"name\":\"test\",\"sql\":{\"key1\":\"value1\",\"key2\":\"value2\"}}";

            bool result = false;
            if (Object.Equals(expected1, actual) || Object.Equals(expected2, actual))
            {
                result = true;
            }
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void TestMarshallMetadata()
        {
            var subsegment = new Subsegment("metadata");
            subsegment.Id = "1111111111111111";
            subsegment.AddMetadata("key1", "value1");
            subsegment.AddMetadata("key2", "value2");
            subsegment.AddMetadata("aws", "key1", "value1");

            var actual = _marshaller.Marshall(subsegment);
            var actualJson = JsonMapper.ToObject(actual.Split('\n')[1]);

            Assert.AreEqual("value1", (string)actualJson["metadata"]["default"]["key1"]);
            Assert.AreEqual("value2", (string)actualJson["metadata"]["default"]["key2"]);
            Assert.AreEqual("value1", (string)actualJson["metadata"]["aws"]["key1"]);
        }

        [TestMethod]
        public void TestMarshallServiceContext()
        {
            var segment = new Segment("service", "1-11111111-111111111111111111111111");
            segment.Service["key"] = "value";
            segment.Id = "1111111111111111";
            segment.StartTime = 100;
            segment.EndTime = 200;

            var expect = "{\"format\":\"json\",\"version\":1}\n{\"trace_id\":\"1-11111111-111111111111111111111111\",\"id\":\"1111111111111111\",\"start_time\":100,\"end_time\":200,\"name\":\"service\",\"service\":{\"key\":\"value\"}}";
            var actual = _marshaller.Marshall(segment);

            Assert.AreEqual(actual, expect);
        }

        [TestMethod]
        public void TestWriteSegmentFields()
        {
            var segment = new Segment("SampleSegment", "1-11111111-111111111111111111111111");
            segment.SetUser("SampleUser");

            var actual = _marshaller.Marshall(segment);
            var actualJson = JsonMapper.ToObject(actual.Split('\n')[1]);

            Assert.AreEqual("SampleUser", (string)actualJson["user"]);
        }

        [TestMethod]
        public void TestWriteSegmentFieldsWithNull()
        {
            var segment = new Segment("SampleSegment", "1-11111111-111111111111111111111111");
            segment.User = null;

            var actual = _marshaller.Marshall(segment);
            var actualJson = JsonMapper.ToObject(actual.Split('\n')[1]);

            Assert.ThrowsException<KeyNotFoundException>(() => (string)actualJson["user"]);
        }
    }
}