using Newtonsoft.Json.Linq;
using OF_DL.Models.Config;
using OF_DL.Models.Downloads;
using OF_DL.Services;
using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased;

namespace OF_DL.Tests.Services;

public class DownloadOrchestrationServiceTests
{
    [Fact]
    public async Task GetAvailableUsersAsync_FiltersIgnoredUsers()
    {
        Config config = CreateConfig(c =>
        {
            c.IncludeExpiredSubscriptions = true;
            c.IgnoredUsersListName = "ignored";
        });
        FakeConfigService configService = new(config);
        ConfigurableApiService apiService = new()
        {
            ActiveSubscriptionsHandler = (_, _) =>
                Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "alice", 1 } }),
            ExpiredSubscriptionsHandler = (_, _) =>
                Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "bob", 2 } }),
            ListsHandler =
                _ => Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "ignored", 10 } }),
            ListUsersHandler = _ => Task.FromResult<List<string>?>(["alice"])
        };
        UserTrackingDbService dbService = new();
        DownloadOrchestrationService service =
            new(apiService, configService, new OrchestrationDownloadServiceStub(), dbService);

        UserListResult result = await service.GetAvailableUsersAsync();

        Assert.Single(result.Users);
        Assert.True(result.Users.ContainsKey("bob"));
        Assert.Null(result.IgnoredListError);
        Assert.NotNull(dbService.CreatedUsers);
        Assert.True(dbService.CreatedUsers.ContainsKey("bob"));
        Assert.False(dbService.CreatedUsers.ContainsKey("alice"));
    }

    [Fact]
    public async Task GetAvailableUsersAsync_SetsIgnoredListErrorWhenMissing()
    {
        Config config = CreateConfig(c =>
        {
            c.IncludeExpiredSubscriptions = false;
            c.IgnoredUsersListName = "missing";
        });
        FakeConfigService configService = new(config);
        ConfigurableApiService apiService = new()
        {
            ActiveSubscriptionsHandler = (_, _) =>
                Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long> { { "alice", 1 } }),
            ListsHandler = _ => Task.FromResult<Dictionary<string, long>?>(new Dictionary<string, long>())
        };
        UserTrackingDbService dbService = new();
        DownloadOrchestrationService service =
            new(apiService, configService, new OrchestrationDownloadServiceStub(), dbService);

        UserListResult result = await service.GetAvailableUsersAsync();

        Assert.NotNull(result.IgnoredListError);
        Assert.Single(result.Users);
        Assert.True(result.Users.ContainsKey("alice"));
    }

    [Fact]
    public async Task GetUsersForListAsync_ReturnsUsersInList()
    {
        FakeConfigService configService = new(CreateConfig());
        ConfigurableApiService apiService = new() { ListUsersHandler = _ => Task.FromResult<List<string>?>(["bob"]) };
        DownloadOrchestrationService service =
            new(apiService, configService, new OrchestrationDownloadServiceStub(), new UserTrackingDbService());
        Dictionary<string, long> allUsers = new() { { "alice", 1 }, { "bob", 2 } };
        Dictionary<string, long> lists = new() { { "mylist", 5 } };

        Dictionary<string, long> result = await service.GetUsersForListAsync("mylist", allUsers, lists);

        Assert.Single(result);
        Assert.Equal(2, result["bob"]);
    }

    [Fact]
    public void ResolveDownloadPath_UsesConfiguredPathWhenSet()
    {
        Config config = CreateConfig(c => c.DownloadPath = "C:\\Downloads");
        DownloadOrchestrationService service =
            new(new ConfigurableApiService(), new FakeConfigService(config), new OrchestrationDownloadServiceStub(),
                new UserTrackingDbService());

        string path = service.ResolveDownloadPath("creator");

        Assert.Equal(Path.Combine("C:\\Downloads", "creator"), path);
    }

    [Fact]
    public void ResolveDownloadPath_UsesDefaultWhenBlank()
    {
        Config config = CreateConfig(c => c.DownloadPath = "");
        DownloadOrchestrationService service =
            new(new ConfigurableApiService(), new FakeConfigService(config), new OrchestrationDownloadServiceStub(),
                new UserTrackingDbService());

        string path = service.ResolveDownloadPath("creator");

        Assert.Equal("__user_data__/sites/OnlyFans/creator", path);
    }

    [Fact]
    public async Task PrepareUserFolderAsync_CreatesFolderAndDb()
    {
        using TempFolder temp = new();
        string userPath = Path.Combine(temp.Path, "creator");
        UserTrackingDbService dbService = new();
        DownloadOrchestrationService service =
            new(new ConfigurableApiService(), new FakeConfigService(CreateConfig()),
                new OrchestrationDownloadServiceStub(), dbService);

        await service.PrepareUserFolderAsync("creator", 99, userPath);

        Assert.True(Directory.Exists(userPath));
        Assert.True(dbService.CheckedUser.HasValue);
        Assert.Equal("creator", dbService.CheckedUser.Value.user.Key);
        Assert.Equal(99, dbService.CheckedUser.Value.user.Value);
        Assert.Equal(userPath, dbService.CheckedUser.Value.path);
        Assert.Contains(userPath, dbService.CreatedDbs);
    }

    [Fact]
    public async Task DownloadSinglePostAsync_WhenMissingPost_SendsMessage()
    {
        ConfigurableApiService apiService = new()
        {
            PostHandler = (_, _) => Task.FromResult(new PostEntities.SinglePostCollection())
        };
        OrchestrationDownloadServiceStub downloadService = new();
        RecordingDownloadEventHandler eventHandler = new();
        DownloadOrchestrationService service =
            new(apiService, new FakeConfigService(CreateConfig()), downloadService, new UserTrackingDbService());

        await service.DownloadSinglePostAsync("creator", 42, "/tmp", new Dictionary<string, long>(),
            true, true, eventHandler);

        Assert.Contains("Getting Post", eventHandler.Messages);
        Assert.Contains("Couldn't find post", eventHandler.Messages);
        Assert.False(downloadService.SinglePostCalled);
    }

    [Fact]
    public async Task DownloadSinglePostAsync_WhenDownloaded_SendsDownloadedMessage()
    {
        PostEntities.SinglePostCollection collection = new()
        {
            SinglePosts = new Dictionary<long, string> { { 1, "https://example.com/post.jpg" } }
        };
        ConfigurableApiService apiService = new() { PostHandler = (_, _) => Task.FromResult(collection) };
        OrchestrationDownloadServiceStub downloadService = new()
        {
            SinglePostResult = new DownloadResult { NewDownloads = 1, TotalCount = 1 }
        };
        RecordingDownloadEventHandler eventHandler = new();
        DownloadOrchestrationService service =
            new(apiService, new FakeConfigService(CreateConfig()), downloadService, new UserTrackingDbService());

        await service.DownloadSinglePostAsync("creator", 99, "/tmp", new Dictionary<string, long>(),
            true, true, eventHandler);

        Assert.Contains("Post 99 downloaded", eventHandler.Messages);
        Assert.True(downloadService.SinglePostCalled);
        Assert.True(eventHandler.ProgressCalls.Count > 0);
    }

    [Fact]
    public async Task DownloadSinglePaidMessageAsync_WithPreviewDownloads()
    {
        PurchasedEntities.SinglePaidMessageCollection collection = new()
        {
            PreviewSingleMessages = new Dictionary<long, string> { { 1, "https://example.com/preview.jpg" } },
            SingleMessages = new Dictionary<long, string> { { 2, "https://example.com/full.jpg" } },
            SingleMessageObjects = [new MessageEntities.SingleMessage()]
        };
        ConfigurableApiService apiService = new() { PaidMessageHandler = (_, _) => Task.FromResult(collection) };
        OrchestrationDownloadServiceStub downloadService = new()
        {
            SinglePaidMessageResult = new DownloadResult { TotalCount = 1, NewDownloads = 1 }
        };
        RecordingDownloadEventHandler eventHandler = new();
        DownloadOrchestrationService service =
            new(apiService, new FakeConfigService(CreateConfig()), downloadService, new UserTrackingDbService());

        await service.DownloadSinglePaidMessageAsync("creator", 5, "/tmp", new Dictionary<string, long>(),
            true, true, eventHandler);

        Assert.Contains(eventHandler.ContentFound, entry => entry.contentType == "Preview Paid Messages");
        Assert.True(downloadService.SinglePaidMessageCalled);
    }

    [Fact]
    public async Task DownloadCreatorContentAsync_DownloadsStoriesWhenEnabled()
    {
        using TempFolder temp = new();
        string path = Path.Combine(temp.Path, "creator");
        Config config = CreateConfig(c =>
        {
            c.DownloadStories = true;
            c.ShowScrapeSize = false;
        });
        FakeConfigService configService = new(config);
        ConfigurableApiService apiService = new()
        {
            MediaHandler = (_, _, _, _) => Task.FromResult<Dictionary<long, string>?>(
                new Dictionary<long, string>
                {
                    { 1, "https://example.com/one.jpg" }, { 2, "https://example.com/two.jpg" }
                })
        };
        OrchestrationDownloadServiceStub downloadService = new()
        {
            StoriesResult = new DownloadResult { TotalCount = 2, NewDownloads = 2 }
        };
        RecordingDownloadEventHandler eventHandler = new();
        DownloadOrchestrationService service =
            new(apiService, configService, downloadService, new UserTrackingDbService());

        CreatorDownloadResult result = await service.DownloadCreatorContentAsync("creator", 1, path,
            new Dictionary<string, long>(), true, true, eventHandler);

        Assert.Equal(2, result.StoriesCount);
        Assert.Contains(eventHandler.ContentFound, entry => entry.contentType == "Stories");
        Assert.Contains(eventHandler.DownloadCompletes, entry => entry.contentType == "Stories");
    }

    [Fact]
    public async Task ResolveUsernameAsync_ReturnsDeletedPlaceholderWhenMissing()
    {
        ConfigurableApiService apiService = new() { UserInfoByIdHandler = _ => Task.FromResult<JObject?>(null) };
        DownloadOrchestrationService service =
            new(apiService, new FakeConfigService(CreateConfig()), new OrchestrationDownloadServiceStub(),
                new UserTrackingDbService());

        string? result = await service.ResolveUsernameAsync(123);

        Assert.Equal("Deleted User - 123", result);
    }

    [Fact]
    public async Task ResolveUsernameAsync_ReturnsUsernameWhenPresent()
    {
        JObject payload = new() { ["5"] = new JObject { ["username"] = "creator" } };
        ConfigurableApiService apiService = new() { UserInfoByIdHandler = _ => Task.FromResult<JObject?>(payload) };
        DownloadOrchestrationService service =
            new(apiService, new FakeConfigService(CreateConfig()), new OrchestrationDownloadServiceStub(),
                new UserTrackingDbService());

        string? result = await service.ResolveUsernameAsync(5);

        Assert.Equal("creator", result);
    }

    private static Config CreateConfig(Action<Config>? configure = null)
    {
        Config config = new()
        {
            DownloadAvatarHeaderPhoto = false,
            DownloadPaidPosts = false,
            DownloadPosts = false,
            DownloadArchived = false,
            DownloadStreams = false,
            DownloadStories = false,
            DownloadHighlights = false,
            DownloadMessages = false,
            DownloadPaidMessages = false,
            DownloadImages = false,
            DownloadVideos = false,
            DownloadAudios = false,
            IncludeExpiredSubscriptions = false,
            IncludeRestrictedSubscriptions = false,
            SkipAds = false,
            IgnoreOwnMessages = false,
            DownloadPostsIncrementally = false,
            BypassContentForCreatorsWhoNoLongerExist = false,
            DownloadDuplicatedMedia = false,
            DownloadOnlySpecificDates = false,
            NonInteractiveModePurchasedTab = false,
            LimitDownloadRate = false,
            FolderPerPaidPost = false,
            FolderPerPost = false,
            FolderPerPaidMessage = false,
            FolderPerMessage = false,
            ShowScrapeSize = false,
            DisableBrowserAuth = false
        };

        configure?.Invoke(config);
        return config;
    }
}
