// Non-nullable field is uninitialized.

// ReSharper disable UnusedParameter.Local
#pragma warning disable CS8618

public class Tests
{
    [ModuleInitializer]
    public static void Initialize()
    {
        VerifierSettings.AddExtraDateTimeFormat("F");
        VerifierSettings.AddExtraDateTimeOffsetFormat("F");
    }

    [Theory]
    [InlineData("a")]
    public Task ReplaceInvalidParamChar(string value) =>
        Verify("foo")
            .UseParameters(Path
                .GetInvalidPathChars()
                .First());

    [Theory]
    [InlineData(1, 2)]
    public Task ParameterCount_TooFew(int one, int two) =>
        Verify("Value")
            .UseParameters(1);

    [Theory]
    [InlineData(1, 2)]
    public async Task IncorrectParameterCount_TooMany(int one, int two)
    {
        var exception = await Assert.ThrowsAsync<Exception>(() => Verify("Value")
            .UseParameters(1, 2, 3));
        Assert.Equal("The number of passed in parameters (3) must not exceed the number of parameters for the method (2).", exception.Message);
    }

    // [Theory]
    // [InlineData(1000.9999d)]
    // public async Task LocalizedParam(decimal value)
    // {
    //     var thread = Thread.CurrentThread;
    //     var culture = thread.CurrentCulture;
    //     thread.CurrentCulture = CultureInfo.GetCultureInfo("de-DE");
    //     try
    //     {
    //         await Verify(value)
    //             .UseParameters(value).ConfigureAwait(true);
    //     }
    //     finally
    //     {
    //         thread.CurrentCulture = culture;
    //     }
    // }

    [ModuleInitializer]
    public static void TreatAsStringInit() =>
        VerifierSettings.TreatAsString<ClassWithToString>(
            (target, _) => target.Property);

    [Fact]
    public Task TreatAsString() =>
        Verify(new ClassWithToString("Foo"));

    record ClassWithToString(string Property);

    [Fact]
    // ReSharper disable once IdentifierTypo
    public Task MisMatchcase()
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            // No way to caseless File.Exists https://github.com/dotnet/core/issues/4596 on linux
            return Task.CompletedTask;
        }

        return Verify("Value");
    }

    static bool onFirstVerifyCalled2;
    static bool onVerifyMismatchCalled2;

    [ModuleInitializer]
    public static void OnVerifyMismatchInit()
    {
        VerifierSettings.OnFirstVerify(
            (filePair, _, _) =>
            {
                if (filePair.VerifiedPath.Contains("OnVerifyMismatch"))
                {
                    onFirstVerifyCalled2 = true;
                }

                return Task.CompletedTask;
            });
        VerifierSettings.OnVerifyMismatch(
            (filePair, _, _) =>
            {
                if (filePair.VerifiedPath.Contains("OnVerifyMismatch"))
                {
                    Assert.NotEmpty(filePair.ReceivedPath);
                    Assert.NotNull(filePair.ReceivedPath);
                    Assert.NotEmpty(filePair.VerifiedPath);
                    Assert.NotNull(filePair.VerifiedPath);
                    onVerifyMismatchCalled2 = true;
                }

                return Task.CompletedTask;
            });
    }

    [Fact]
    public async Task OnVerifyMismatch()
    {
        var settings = new VerifySettings();
        settings.DisableDiff();
        await Assert.ThrowsAsync<VerifyException>(() => Verify("value", settings));
        Assert.False(onFirstVerifyCalled2);
        Assert.True(onVerifyMismatchCalled2);
    }

    [Fact]
    public async Task OnCallbacksTest()
    {
        var onVerifyBeforeCalled = false;
        var onVerifyAfterCalled = false;
        var settings = new VerifySettings();
        settings.OnVerify(
            before: () => onVerifyBeforeCalled = true,
            after: () => onVerifyAfterCalled = true);

        await Verify("value", settings);
        Assert.True(onVerifyBeforeCalled);
        Assert.True(onVerifyAfterCalled);
    }

    #region OnInstanceHandlers

    [Fact]
    public Task OnCallbacks()
    {
        var settings = new VerifySettings();
        settings.OnVerify(
            before: () => Debug.WriteLine("before"),
            after: () => Debug.WriteLine("after"));
        settings.OnFirstVerify(
            (receivedFile, receivedText, autoVerify) =>
            {
                Debug.WriteLine(receivedFile);
                Debug.WriteLine(receivedText);
                return Task.CompletedTask;
            });
        settings.OnVerifyMismatch(
            (filePair, message, autoVerify) =>
            {
                Debug.WriteLine(filePair.ReceivedPath);
                Debug.WriteLine(filePair.VerifiedPath);
                Debug.WriteLine(message);
                return Task.CompletedTask;
            });

        return Verify("value", settings);
    }

    #endregion

    #region OnFluentHandlers

    [Fact]
    public Task OnFluentCallbacks() =>
        Verify("value")
            .OnVerify(
                before: () => Debug.WriteLine("before"),
                after: () => Debug.WriteLine("after"))
            .OnFirstVerify(
                (receivedFile, receivedText, autoVerify) =>
                {
                    Debug.WriteLine(receivedFile);
                    Debug.WriteLine(receivedText);
                    return Task.CompletedTask;
                })
            .OnVerifyMismatch(
                (filePair, message, autoVerify) =>
                {
                    Debug.WriteLine(filePair.ReceivedPath);
                    Debug.WriteLine(filePair.VerifiedPath);
                    Debug.WriteLine(message);
                    return Task.CompletedTask;
                });

    #endregion

    // ReSharper restore UnusedParameter.Local

#if NET6_0_OR_GREATER
    static bool onFirstVerifyCalled;
    static bool onVerifyMismatchCalled;

    [ModuleInitializer]
    public static void OnFirstVerifyInit()
    {
        VerifierSettings.OnFirstVerify(
            (filePair, _, _) =>
            {
                if (filePair.VerifiedPath.Contains("OnFirstVerify"))
                {
                    Assert.NotEmpty(filePair.ReceivedPath);
                    Assert.NotNull(filePair.ReceivedPath);
                    onFirstVerifyCalled = true;
                }

                return Task.CompletedTask;
            });
        VerifierSettings.OnVerifyMismatch(
            (filePair, _, _) =>
            {
                if (filePair.VerifiedPath.Contains("OnFirstVerify"))
                {
                    onVerifyMismatchCalled = true;
                }

                return Task.CompletedTask;
            });
    }

    [Fact]
    public async Task OnFirstVerify()
    {
        var settings = new VerifySettings();
        settings.DisableDiff();
        await Assert.ThrowsAsync<VerifyException>(() => Verify("value", settings));
        Assert.True(onFirstVerifyCalled);
        Assert.False(onVerifyMismatchCalled);
    }

#endif

    [ModuleInitializer]
    public static void SettingsArePassedInit() =>
        VerifierSettings.RegisterStreamComparer(
            "SettingsArePassed",
            (_, _, _) => Task.FromResult(new CompareResult(true)));

    [Fact]
    public Task StringBuilder() =>
        Verify(new StringBuilder("value"));

    [Fact]
    public Task NestedStringBuilder() =>
        Verify(new
        {
            StringBuilder = new StringBuilder("value")
        });

    [Fact]
    public Task TextWriter()
    {
        var target = new StringWriter();
        target.Write("content");
        return Verify(target);
    }

    [Fact]
    public Task NestedTextWriter()
    {
        var target = new StringWriter();
        target.Write("content");
        return Verify(new
        {
            target
        });
    }

    [Fact]
    public async Task ShouldNotIgnoreCase()
    {
        await Verify("A");
        var settings = new VerifySettings();
        settings.DisableDiff();
        PrefixUnique.Clear();
        await Assert.ThrowsAsync<VerifyException>(() => Verify("a", settings));
    }

    [Fact]
    public async Task DanglingFiles()
    {
        var receivedFile = CurrentFile.Relative($"Tests.DanglingFiles.{Namer.RuntimeAndVersion}.received.txt");
        var verifiedNewFile = CurrentFile.Relative($"Tests.DanglingFiles.{Namer.RuntimeAndVersion}#01.verified.txt");
        File.WriteAllText(receivedFile, "");
        File.WriteAllText(verifiedNewFile, "");
        await Verify("value")
            .UniqueForRuntimeAndVersion()
            .AutoVerify();
        Assert.False(File.Exists(receivedFile));
        Assert.False(File.Exists(verifiedNewFile));
    }

    [Theory]
    [InlineData("param")]
    public async Task DanglingFilesIgnoreParametersForVerified(string param)
    {
        var receivedFile = CurrentFile.Relative($"Tests.DanglingFilesIgnoreParametersForVerified_param=param.{Namer.RuntimeAndVersion}#01.received.txt");
        var verifiedFile = CurrentFile.Relative($"Tests.DanglingFilesIgnoreParametersForVerified.{Namer.RuntimeAndVersion}#01.verified.txt");
        File.WriteAllText(receivedFile, "");
        File.WriteAllText(verifiedFile, "");
        await Verify("value")
            .UniqueForRuntimeAndVersion()
            .IgnoreParametersForVerified(param)
            .AutoVerify();
        await Task.Delay(1000);
        Assert.False(File.Exists(receivedFile));
        Assert.False(File.Exists(verifiedFile));
    }

#if NET9_0

    [Theory]
    [InlineData("P1", "P2")]
    public async Task DanglingFilesIgnoreParameters(string param1, string param2)
    {
        var receivedFile = CurrentFile.Relative($"Tests.DanglingFilesIgnoreParameters_param1=P1_param2=P2.{Namer.RuntimeAndVersion}#01.received.txt");
        var verifiedFile = CurrentFile.Relative($"Tests.DanglingFilesIgnoreParameters_param2=P2.{Namer.RuntimeAndVersion}#01.verified.txt");
        File.WriteAllText(receivedFile, "");
        File.WriteAllText(verifiedFile, "");
        await Verify("value")
            .UniqueForRuntimeAndVersion()
            .UseParameters(param1, param2)
            .IgnoreParameters(nameof(param1))
            .AutoVerify();
        await Task.Delay(1000);
        Assert.False(File.Exists(receivedFile));
        Assert.False(File.Exists(verifiedFile));
    }

#endif

    class Element
    {
        // ReSharper disable once UnusedMember.Local
        public string? Id { get; set; }
    }

#if NET6_0_OR_GREATER
    [Fact]
    public async Task StringWithUtf8Bom()
    {
        var utf8 = Encoding.UTF8;
        var preamble = utf8.GetString(utf8.GetPreamble());
        await Verify($"{preamble}a")
            .AutoVerify();
        await Verify("a")
            .DisableRequireUniquePrefix();
    }

#endif

    [Fact]
    public async Task EnsureUtf8BomPreamble()
    {
        if (BuildServerDetector.Detected)
        {
            return;
        }

        var file = CurrentFile.Relative($"Tests.{nameof(EnsureUtf8BomPreamble)}.{Namer.RuntimeAndVersion}.verified.txt");
        File.Delete(file);
        await Verify("value")
            .UniqueForRuntimeAndVersion()
            .AutoVerify();
        var fileBytes = File
            .ReadAllBytes(file)
            .Take(3);
        var preambleBytes = Encoding.UTF8.GetPreamble();
        Assert.Equal(preambleBytes, fileBytes);
        File.Delete(file);
    }

    [Fact]
    public Task StringExtension() =>
        Verify("<a>b</a>", "xml");

    [Fact]
    public Task FuncOfTaskResult()
    {
        var target = async () =>
        {
            await Task.Delay(1);
            return "value";
        };
        return Verify(target);
    }

    [Fact]
    public Task TaskResult()
    {
        var target = Task.FromResult<string>("value");
        return Verify(target);
    }

    static async IAsyncEnumerable<string> AsyncEnumerableMethod()
    {
        await Task.Delay(1);
        yield return "one";
        await Task.Delay(1);
        yield return "two";
    }

    [Fact]
    public Task AsyncEnumerable() =>
        Verify(AsyncEnumerableMethod());

    static async IAsyncEnumerable<DisposableTarget> AsyncEnumerableDisposableMethod(DisposableTarget target)
    {
        await Task.Delay(1);
        yield return target;
    }

    [Fact]
    public async Task AsyncEnumerableDisposable()
    {
        var target = new DisposableTarget();
        await Verify(AsyncEnumerableDisposableMethod(target));
        Assert.True(target.Disposed);
    }

    [Fact]
    public async Task Result()
    {
        #region VerifyResult

        var result = await Verify(
            new
            {
                Property = "Value To Check"
            });
        Assert.Contains("Value To Check", result.Text);

        #endregion

        Assert.NotNull(result.Target);
    }

    [Fact]
    public async Task ResultAutoVerify()
    {
        var result = await Verify("Value")
            .AutoVerify();

        Assert.Single(result.Files);
    }

    [Fact]
    public async Task ResultAutoVerifyMissingVerified()
    {
        if (ContinuousTestingDetector.Detected)
        {
            return;
        }

        try
        {
            var result = await Verify("Value")
                .UniqueForRuntimeAndVersion()
                .AutoVerify();

            Assert.Single(result.Files);
        }
        finally
        {
            var file = CurrentFile.Relative($"Tests.ResultAutoVerifyMissingVerified.{Namer.RuntimeAndVersion}.verified.txt");
            File.Delete(file);
        }
    }

    static async IAsyncEnumerable<AsyncDisposableTarget> AsyncEnumerableAsyncDisposableMethod(AsyncDisposableTarget target)
    {
        await Task.Delay(1);
        yield return target;
    }

    [Fact]
    public async Task AsyncEnumerableAsyncDisposable()
    {
        var target = new AsyncDisposableTarget();
        await Verify(AsyncEnumerableAsyncDisposableMethod(target));
        Assert.True(target.AsyncDisposed);
    }

    [Fact]
    public async Task FuncOfTaskResultAsyncDisposable()
    {
        var disposableTarget = new AsyncDisposableTarget();
        var target = async () =>
        {
            await Task.Delay(1);
            return disposableTarget;
        };
        await Verify(target);
        Assert.True(disposableTarget.AsyncDisposed);
    }

    [Fact]
    public async Task TaskResultAsyncDisposable()
    {
        var disposableTarget = new AsyncDisposableTarget();
        var target = Task.FromResult(disposableTarget);
        await Verify(target);
        Assert.True(disposableTarget.AsyncDisposed);
    }

    class AsyncDisposableTarget :
        IAsyncDisposable,
        IDisposable
    {
#pragma warning disable 414
        public string Property = "Value";
#pragma warning restore 414
        public bool AsyncDisposed;

        public ValueTask DisposeAsync()
        {
            AsyncDisposed = true;
            return new();
        }

        public void Dispose() =>
            throw new();
    }

    [Fact]
    public async Task FuncOfTaskResultDisposable()
    {
        var disposableTarget = new DisposableTarget();
        var target = async () =>
        {
            await Task.Delay(1);
            return disposableTarget;
        };
        await Verify(target);
        Assert.True(disposableTarget.Disposed);
    }

    [Fact]
    public async Task TaskResultDisposable()
    {
        var disposableTarget = new DisposableTarget();
        var target = Task.FromResult(disposableTarget);
        await Verify(target);
        Assert.True(disposableTarget.Disposed);
    }

    class DisposableTarget :
        IDisposable
    {
#pragma warning disable 414
        public string Property = "Value";
#pragma warning restore 414
        public bool Disposed;

        public void Dispose() =>
            Disposed = true;
    }

    //[Fact(Skip = "explicit")]
    //public async Task ShouldUseExtraSettings()
    //{
    //    ApplyExtraSettings(settings => { settings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat; });

    //    var person = new Person
    //    {
    //        Dob = new DateTime(1980, 5, 5, 1, 1, 1)
    //    };
    //    DontScrubDateTimes();
    //    await Verify(person);
    //}
}