// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Microsoft.Generator.CSharp.Expressions;
using System.IO;

namespace Microsoft.Generator.CSharp.Tests
{
    public class CodeWriterTests
    {
        internal const string NewLine = "\n";
        private CodeWriter _codeWriter = null!;
        private readonly string _mocksFolder = "mocks";
        private readonly string _autoGenerated = "// <auto-generated/>";
        private readonly string _nullableDisable = "#nullable disable";
        private readonly string _licenseString = "License string";
        private string? _header;

        [SetUp]
        public void Setup()
        {
            _codeWriter = new CodeWriter();

            _header = new StringBuilder()
                .Append("// ")
                .Append(_licenseString)
                .Append(_autoGenerated).Append(NewLine)
                .Append(NewLine)
                .Append(_nullableDisable).Append(NewLine)
                .Append(NewLine)
                .ToString();

            var configFilePath = Path.Combine(AppContext.BaseDirectory, _mocksFolder);
            // initialize the singleton instance of the plugin
            _ = new MockCodeModelPlugin(new GeneratorContext(Configuration.Load(configFilePath)));
        }

        [Test]
        public void GeneratesNewNamesInChildScope()
        {
            var cwd1 = new CodeWriterDeclaration("a");
            var cwd2 = new CodeWriterDeclaration("a");
            _codeWriter.WriteLine($"{cwd1:D}");
            using (_codeWriter.Scope())
            {
                _codeWriter.WriteLine($"{cwd2:D}");
            }

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("a").Append(NewLine);
            sb.Append("{").Append(NewLine);
            sb.Append("a0").Append(NewLine);
            sb.Append("}").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void ScopeLineIsInsideScope()
        {
            var cwd1 = new CodeWriterDeclaration("a");
            var cwd2 = new CodeWriterDeclaration("a");
            using (_codeWriter.Scope($"{cwd1:D}"))
            {
            }

            using (_codeWriter.Scope($"{cwd2:D}"))
            {
            }

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("a").Append(NewLine);
            sb.Append("{").Append(NewLine);
            sb.Append("}").Append(NewLine);
            sb.Append("a").Append(NewLine);
            sb.Append("{").Append(NewLine);
            sb.Append("}").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void VariableNameNotReusedWhenUsedInChildScope()
        {
            var cwd1 = new CodeWriterDeclaration("a");
            var cwd2 = new CodeWriterDeclaration("a");
            using (_codeWriter.Scope())
            {
                _codeWriter.WriteLine($"{cwd1:D}");
            }

            _codeWriter.WriteLine($"{cwd2:D}");

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("{").Append(NewLine);
            sb.Append("a").Append(NewLine);
            sb.Append("}").Append(NewLine);
            sb.Append("a0").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void CorrectlyHandlesCurlyBraces()
        {
            _codeWriter.Append($"public {typeof(string)} Data {{ get; private set; }}");
            var expected = "public string Data { get; private set; }" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(),
                _codeWriter.ToString());
        }

        [Test]
        public void FormatInFormat()
        {
            FormattableString fs1 = $"'1' is {typeof(int)}";
            FormattableString fs2 = $"'a' is {typeof(char)} and {fs1} and 'true' is {typeof(bool)}";

            _codeWriter.Append(fs2);
            var expected = "'a' is char and '1' is int and 'true' is bool" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }


        [Test]
        public void EnumerableFormatInFormat()
        {
            _codeWriter.Append($"Multiply:{Enumerable.Range(1, 4).Select(i => (FormattableString)$" {i} * 2 = {i * 2};")}");
            var expected = "Multiply: 1 * 2 = 2; 2 * 2 = 4; 3 * 2 = 6; 4 * 2 = 8;" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void SingleLineSummary()
        {
            _codeWriter.WriteXmlDocumentationSummary($"Some {typeof(string)} summary.");
            var expected = "/// <summary> Some string summary. </summary>" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void NoEmptySummary()
        {
            _codeWriter.WriteXmlDocumentationSummary($"{string.Empty}");
            var expected = string.Empty;
            var sb = new StringBuilder();
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [TestCase(typeof(string), false, "<see cref=\"string\"/>", "")]
        [TestCase(typeof(int), false, "<see cref=\"int\"/>", "")]
        [TestCase(typeof(int), true, "<see cref=\"int\"/>?", "")]
        [TestCase(typeof(List<>), false, "<see cref=\"global::System.Collections.Generic.List{T}\"/>", "System.Collections.Generic")]
        [TestCase(typeof(KeyValuePair<,>), false, "<see cref=\"global::System.Collections.Generic.KeyValuePair{TKey,TValue}\"/>", "System.Collections.Generic")]
        [TestCase(typeof(KeyValuePair<int, string>), true, "<see cref=\"global::System.Collections.Generic.KeyValuePair{TKey,TValue}\"/>? where <c>TKey</c> is of type <see cref=\"int\"/>, where <c>TValue</c> is of type <see cref=\"string\"/>", "System.Collections.Generic")]
        public void SeeCRefType(Type type, bool isNullable, string expectedWritten, string ns)
        {
            var csType = new CSharpType(type).WithNullable(isNullable);
            _codeWriter.WriteXmlDocumentationSummary($"Some {csType:C} summary.");
            var expected = $"/// <summary> Some {expectedWritten} summary. </summary>" + NewLine;
            var sb = new StringBuilder();
            sb.Append(_header);
            if (!string.IsNullOrEmpty(ns))
            {
                sb.Append("using ");
                sb.Append(ns);
                sb.Append(";").Append(NewLine);
                sb.Append(NewLine);
            }
            sb.Append(expected);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        [Test]
        public void MultiLineSummary()
        {
            FormattableString fs1 = $@"L04
L05
L06 {typeof(int)}


L09";
            FormattableString fs2 = $@"

L11 {typeof(bool)}
L12

";
            IEnumerable<FormattableString> fss = new[] { fs1, fs2 };
            FormattableString fs = $@"L00
L01
L02 {typeof(string)}

{fss}
L15
L16";
            _codeWriter.WriteXmlDocumentationSummary(fs);

            var sb = new StringBuilder();
            sb.Append(_header);
            sb.Append("/// <summary>").Append(NewLine);
            sb.Append("/// L00").Append(NewLine);
            sb.Append("/// L01").Append(NewLine);
            sb.Append("/// L02 string").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L04").Append(NewLine);
            sb.Append("/// L05").Append(NewLine);
            sb.Append("/// L06 int").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L09").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L11 bool").Append(NewLine);
            sb.Append("/// L12").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("///").Append(NewLine);
            sb.Append("/// L15").Append(NewLine);
            sb.Append("/// L16").Append(NewLine);
            sb.Append("/// </summary>").Append(NewLine);

            Assert.AreEqual(sb.ToString(), _codeWriter.ToString());
        }

        // Validate that the WriteMethodDeclarationNoScope method correctly writes a custom constructor signature method
        [Test]
        public void TestWriteMethodDeclarationNoScope_ConstructorSignature()
        {
            var baseInitializerStatement = new ConstructorInitializer(true, new List<ValueExpression> { new StringLiteralExpression("test", false) });
            var constructorSignature = new ConstructorSignature(new CSharpType(typeof(string)), $"Test constructor summary", $"Test description",
                MethodSignatureModifiers.Public, Array.Empty<Parameter>(), null, baseInitializerStatement);
            var codeWriter = new CodeWriter();
            codeWriter.WriteMethodDeclarationNoScope(constructorSignature);

            var expected = new StringBuilder()
               .Append(_header)
               .Append("public String(): base(\"test\")").Append(NewLine)
               .ToString();
            var result = codeWriter.ToString();
            Assert.AreEqual(expected, result);
        }
    }
}
