using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using Xunit.Sdk;

namespace UnitTests;

/// <summary>
///     Enables test functions annotated with the [AutoInitShutdown] attribute to
///     automatically call Application.Init at start of the test and Application.Shutdown after the
///     test exits.
///     This is necessary because a) Application is a singleton and Init/Shutdown must be called
///     as a pair, and b) all unit test functions should be atomic..
/// </summary>
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
{
    /// <summary>
    ///     Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown
    ///     are automatically called Before/After a test runs.
    /// </summary>
    /// <param name="autoInit">If true, Application.Init will be called Before the test runs.</param>
    /// <param name="forceDriver">
    ///     Forces the specified driver ("windows", "dotnet", "unix", or "fake") to
    ///     be used when Application.Init is called. If not specified FakeDriver will be used. Only valid if
    ///     <paramref name="autoInit"/> is true.
    /// </param>
    /// <param name="verifyShutdown">If true and <see cref="Application.Initialized"/> is true, the test will fail.</param>
    public AutoInitShutdownAttribute (
        bool autoInit = true,
        string forceDriver = null,
        bool verifyShutdown = false
    )
    {
        AutoInit = autoInit;
        CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
        _forceDriver = forceDriver;
        _verifyShutdown = verifyShutdown;
    }

    private readonly bool _verifyShutdown;
    private readonly string _forceDriver;
    private IDisposable _v2Cleanup;

    public override void After (MethodInfo methodUnderTest)
    {
        Debug.WriteLine ($"After: {methodUnderTest?.Name ?? "Unknown Test"}");

        // Turn off diagnostic flags in case some test left them on
        View.Diagnostics = ViewDiagnosticFlags.Off;

        _v2Cleanup?.Dispose ();

        if (AutoInit)
        {
            // try
            {
                if (!_verifyShutdown)
                {
                    Application.ResetState (ignoreDisposed: true);
                }

                Application.Shutdown ();
#if DEBUG_IDISPOSABLE
                if (View.Instances.Count == 0)
                {
                    Assert.Empty (View.Instances);
                }
                else
                {
                    View.Instances.Clear ();
                }
#endif
            }
            //catch (Exception e)
            //{
            //    Assert.Fail ($"Application.Shutdown threw an exception after the test exited: {e}");
            //}
            //finally
            {
#if DEBUG_IDISPOSABLE
                View.Instances.Clear ();
                Application.ResetState (true);
#endif
            }
        }

        Debug.Assert (!CM.IsEnabled, "This test left ConfigurationManager enabled!");

        // Force the ConfigurationManager to reset to its hardcoded defaults
        CM.Disable (true);
    }

    public override void Before (MethodInfo methodUnderTest)
    {
        Debug.WriteLine ($"Before: {methodUnderTest?.Name ?? "Unknown Test"}");

        Debug.Assert (!CM.IsEnabled, "A previous test left ConfigurationManager enabled!");

        // Disable & force the ConfigurationManager to reset to its hardcoded defaults
        CM.Disable (true);

        //Debug.Assert(!CM.IsEnabled, "Some other test left ConfigurationManager enabled.");

        if (AutoInit)
        {
#if DEBUG_IDISPOSABLE
            View.EnableDebugIDisposableAsserts = true;

            // Clear out any lingering Responder instances from previous tests
            if (View.Instances.Count == 0)
            {
                Assert.Empty (View.Instances);
            }
            else
            {
                View.Instances.Clear ();
            }
#endif
            if (string.IsNullOrEmpty(_forceDriver) || _forceDriver.ToLowerInvariant () == "fake")
            {
                var fa = new FakeApplicationFactory ();
                _v2Cleanup = fa.SetupFakeApplication ();

            }
            else
            {
                Assert.Fail ("Specifying driver name not yet supported");
                //Application.Init ((IDriver)Activator.CreateInstance (_forceDriver));
            }
        }
    }

    private bool AutoInit { get; }

    /// <summary>
    /// Runs a single iteration of the main loop (layout, draw, run timed events etc.)
    /// </summary>
    public static void RunIteration ()
    {
        ApplicationImpl a = (ApplicationImpl)ApplicationImpl.Instance;
        a.Coordinator?.RunIteration ();
    }
}