// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;

namespace osu.Game.Tests.Visual.Background
{
    public partial class TestSceneSeasonalBackgroundLoader : ScreenTestScene
    {
        [Resolved]
        private OsuConfigManager config { get; set; }

        [Resolved]
        private SessionStatics statics { get; set; }

        private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;

        private LookupLoggingTextureStore textureStore;
        private SeasonalBackgroundLoader backgroundLoader;
        private Container backgroundContainer;

        // in real usages these would be online URLs, but correct execution of this test
        // shouldn't be coupled to existence of online assets.
        private static readonly List<string> seasonal_background_urls = new List<string>
        {
            "Backgrounds/bg2",
            "Backgrounds/bg4",
            "Backgrounds/bg3"
        };

        protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
        {
            var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));

            textureStore = new LookupLoggingTextureStore(dependencies.Get<IRenderer>());
            dependencies.CacheAs(typeof(LargeTextureStore), textureStore);

            return dependencies;
        }

        [BackgroundDependencyLoader]
        private void load(LargeTextureStore wrappedStore)
        {
            textureStore.AddStore(wrappedStore);

            Child = new DependencyProvidingContainer
            {
                CachedDependencies = new (Type, object)[]
                {
                    (typeof(LargeTextureStore), textureStore)
                },
                Child = backgroundContainer = new Container
                {
                    RelativeSizeAxes = Axes.Both
                }
            };
        }

        [SetUp]
        public void SetUp() => Schedule(() =>
        {
            // reset API response in statics to avoid test crosstalk.
            statics.SetValue<APISeasonalBackgrounds>(Static.SeasonalBackgrounds, null);
            textureStore.PerformedLookups.Clear();
            dummyAPI.SetState(APIState.Online);

            backgroundContainer.Clear();
        });

        [TestCase(-5)]
        [TestCase(5)]
        public void TestAlwaysSeasonal(int daysOffset)
        {
            registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
            setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);

            createLoader();

            for (int i = 0; i < 4; ++i)
                loadNextBackground();

            AddAssert("all backgrounds cycled", () => new HashSet<string>(textureStore.PerformedLookups).SetEquals(seasonal_background_urls));
        }

        [TestCase(-5)]
        [TestCase(5)]
        public void TestNeverSeasonal(int daysOffset)
        {
            registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
            setSeasonalBackgroundMode(SeasonalBackgroundMode.Never);

            createLoader();

            assertNoBackgrounds();
        }

        [Test]
        public void TestSometimesInSeason()
        {
            registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5));
            setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);

            createLoader();

            assertAnyBackground();
        }

        [Test]
        public void TestSometimesOutOfSeason()
        {
            registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10));
            setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);

            createLoader();

            assertNoBackgrounds();
        }

        [Test]
        public void TestDelayedConnectivity()
        {
            registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30));
            setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
            AddStep("go offline", () => dummyAPI.SetState(APIState.Offline));

            createLoader();
            assertNoBackgrounds();

            AddStep("go online", () => dummyAPI.SetState(APIState.Online));

            assertAnyBackground();
        }

        private void registerBackgroundsResponse(DateTimeOffset endDate)
            => AddStep("setup request handler", () =>
            {
                dummyAPI.HandleRequest = request =>
                {
                    if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
                        return false;

                    backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
                    {
                        Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
                        EndDate = endDate
                    });

                    return true;
                };
            });

        private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
            => AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode));

        private void createLoader()
            => AddStep("create loader", () =>
            {
                if (backgroundLoader != null)
                    Remove(backgroundLoader, true);

                Add(backgroundLoader = new SeasonalBackgroundLoader());
            });

        private void loadNextBackground()
        {
            SeasonalBackground previousBackground = null;
            SeasonalBackground background = null;

            AddStep("create next background", () =>
            {
                previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault();
                background = backgroundLoader.LoadNextBackground();
                LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
            });

            AddUntilStep("background loaded", () => background.IsLoaded);
            AddAssert("background is different", () => !background.Equals(previousBackground));
        }

        private void assertAnyBackground()
        {
            loadNextBackground();
            AddAssert("background looked up", () => textureStore.PerformedLookups.Any());
        }

        private void assertNoBackgrounds()
        {
            AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null);
            AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any());
        }

        private class LookupLoggingTextureStore : LargeTextureStore
        {
            public List<string> PerformedLookups { get; } = new List<string>();

            public LookupLoggingTextureStore(IRenderer renderer)
                : base(renderer)
            {
            }

            public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
            {
                PerformedLookups.Add(name);
                return base.Get(name, wrapModeS, wrapModeT);
            }
        }
    }
}
