using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Allure.Net.Commons;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestResult = Allure.Net.Commons.TestResult;

namespace skyscraper8.Tests;

[TestClass]
public class Feyllure
{
    public TestContext TestContext { get; set; }
    
    protected static readonly AllureLifecycle Allure = AllureLifecycle.Instance;

    private bool assemblyWasInited; 
    private void AssemblyInit(TestContext context)
    {
        // Ensure allure-results exists
        var resultsDir = Allure.ResultsDirectory;
        Directory.CreateDirectory(resultsDir);

        var envFile = Path.Combine(resultsDir, "environment.properties");

        using (var writer = new StreamWriter(envFile))
        {
            writer.WriteLine($"SkyscraperRelease={VersionInfo.GetPublicReleaseNumber()}");
            writer.WriteLine($"SkyscraperCodeVersion={VersionInfo.GetCurrentAssemblyDisplayVersion()}");
            writer.WriteLine($"OS={RuntimeInformation.OSDescription}");
            writer.WriteLine($".NET={Environment.Version}");
            writer.WriteLine($"Machine={Environment.MachineName}");
            writer.WriteLine($"User={Environment.UserName}");
            writer.WriteLine($"Framework={System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
            writer.WriteLine($"BuildDate={DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}");
        }
        
        var executorFile = Path.Combine(resultsDir, "executor.json");
        var executorInfo = new
        {
            name = Environment.MachineName,
            buildName = "My build name",
            buildUrl = "http://127.0.0.2",
        };
        var executorJson = Newtonsoft.Json.JsonConvert.SerializeObject(executorInfo);
        File.WriteAllText(executorFile, executorJson);
    }
    
    [TestInitialize]
    public void Setup()
    {
        if (!assemblyWasInited){
            AssemblyInit(TestContext);
            assemblyWasInited = true;
        }
        var testName = TestContext.TestName;
        var fqClass = TestContext.FullyQualifiedTestClassName;

        // Extract namespace and class name
        var lastDot = fqClass.LastIndexOf('.');
        var ns = lastDot > 0 ? fqClass.Substring(0, lastDot) : "";
        var cls = lastDot > 0 ? fqClass.Substring(lastDot + 1) : fqClass;
        
        TestResult testResult = new TestResult();
        testResult.uuid = Guid.NewGuid().ToString();
        testResult.name = testName;
        testResult.fullName = String.Format("{0}.{1}", TestContext.FullyQualifiedTestClassName, testName);
        testResult.labels = new List<Label>()
        {
            //Packages Tab
            new Label { name = "package", value = ns},
            new Label { name = "testClass", value = cls},
            new Label { name = "testMethod", value = testName},
            
            //Suites tab
            new Label { name = "parentSuite", value = ns},
            new Label { name = "suite", value = cls},
        };
        
        Allure.StartTestCase(testResult);
        
        descriptionWriter = null;
    }

    [TestCleanup]
    public void TearDown()
    {
        switch (TestContext.CurrentTestOutcome)
        {
            case UnitTestOutcome.Passed:
                Allure.UpdateTestCase(tc => tc.status = Status.passed);
                break;
            case UnitTestOutcome.Failed:
                Allure.UpdateTestCase(tc => tc.status = Status.failed);
                break;
            case UnitTestOutcome.Inconclusive:
                Allure.UpdateTestCase(tc => tc.status = Status.skipped);
                Print("\n\n" + TestContext.TestException.ToString());
                break;
            default:
                StepResult stepResult = new StepResult();
                stepResult.name = String.Format("Current Test Outcome: {0}", TestContext.CurrentTestOutcome);;
                Allure.UpdateTestCase(tc =>
                {
                    tc.status = Status.skipped;
                    tc.steps.Add(stepResult);
                });
                break;
        }

        if (descriptionWriter != null)
        {
            Allure.UpdateTestCase(tc => { tc.description = descriptionWriter.ToString(); });
        }

        Allure.StopTestCase();
        Allure.WriteTestCase();
    }

    private StringWriter descriptionWriter;
    public void Print(string message, params object[] args)
    {
        if (descriptionWriter == null)
            descriptionWriter = new StringWriter();
        string formattedMessage = String.Format(message, args);
        Console.WriteLine(formattedMessage);
        descriptionWriter.WriteLine(formattedMessage);
    }

    private DirectoryInfo testsuitePath;
    protected DirectoryInfo GetTestsuitePath()
    {
        Print("Checking whether skyscraper-testsuite exists...");
        if (Directory.Exists("skyscraper-testsuite"))
        {
            testsuitePath = new DirectoryInfo("skyscraper8-testsuite");
            return testsuitePath;
        }

        Print("Checking TESTSUITE_PATH...");
        string directory = Environment.GetEnvironmentVariable("TESTSUITE_PATH");
        if (!string.IsNullOrEmpty(directory))
        {
            testsuitePath = new DirectoryInfo(directory);
            return testsuitePath;
        }

        FileInfo pointerFile = new FileInfo("skyscraper8-testsuite-path.txt");
        Print("Checking {0}...",pointerFile.FullName);
        if (File.Exists(pointerFile.FullName))
        {
            string readAllText = File.ReadAllText(pointerFile.FullName);
            readAllText = readAllText.Trim();
            testsuitePath = new DirectoryInfo(readAllText);
            return testsuitePath;
        }
        
        Assert.Inconclusive("Could not find the test suite path.");
        return null;
    }

    protected FileStream GetStreamSample(string streamName)
    {
        DirectoryInfo testsuitePath = GetTestsuitePath();
        string combine = Path.Combine(testsuitePath.FullName, streamName);
        FileInfo result = new FileInfo(combine);
        if (!result.Exists)
        {
            Assert.Inconclusive(String.Format("Could not find {0}",result.FullName));
            return null;
        }
        
        return result.OpenRead();
    }

    protected void AssertTargetInvocation<T>(Action action)
        where T: Exception
    {
        Assert.Throws<T>(() =>
        {
            try
            {
                action();
            }
            catch (TargetInvocationException tie)
            {
                throw tie.InnerException;
            }
        });
    }
}
