using Newtonsoft.Json.Linq;
using OF_DL.Enumerations;
using OF_DL.Models;
using OF_DL.Models.Config;
using OF_DL.Models.Downloads;
using OF_DL.Services;
using Serilog.Core;
using Serilog.Events;
using ArchivedEntities = OF_DL.Models.Entities.Archived;
using MessageEntities = OF_DL.Models.Entities.Messages;
using PostEntities = OF_DL.Models.Entities.Posts;
using PurchasedEntities = OF_DL.Models.Entities.Purchased;
using StreamEntities = OF_DL.Models.Entities.Streams;
using UserEntities = OF_DL.Models.Entities.Users;

namespace OF_DL.Tests.Services;

internal sealed class TempFolder : IDisposable
{
    public TempFolder()
    {
        Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "ofdl-tests", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(Path);
    }

    public string Path { get; }

    public void Dispose()
    {
        try
        {
            Directory.Delete(Path, true);
        }
        catch
        {
            // ignored
        }
    }
}

internal sealed class ProgressRecorder : IProgressReporter
{
    public long Total { get; private set; }

    public void ReportProgress(long increment) => Total += increment;
}

internal sealed class FakeConfigService(Config config) : IConfigService
{
    public Config CurrentConfig { get; private set; } = config;

    public bool IsCliNonInteractive { get; } = config.NonInteractiveMode;

    public Task<bool> LoadConfigurationAsync(string[] args) => Task.FromResult(true);

    public Task SaveConfigurationAsync(string filePath = "config.conf") => Task.CompletedTask;

    public void UpdateConfig(Config newConfig) => CurrentConfig = newConfig;

    public List<(string Name, bool Value)> GetToggleableProperties() => [];

    public bool ApplyToggleableSelections(List<string> selectedNames) => false;
}

internal sealed class MediaTrackingDbService : IDbService
{
    public bool CheckDownloadedResult { get; init; }

    public long StoredFileSize { get; init; }

    public (string folder, long mediaId, string apiType, string directory, string filename, long size,
        bool downloaded, DateTime createdAt)? LastUpdateMedia { get; private set; }

    public Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename,
        long size, bool downloaded, DateTime createdAt)
    {
        LastUpdateMedia = (folder, mediaId, apiType, directory, filename, size, downloaded, createdAt);
        return Task.CompletedTask;
    }

    public Task<long> GetStoredFileSize(string folder, long mediaId, string apiType) =>
        Task.FromResult(StoredFileSize);

    public Task<bool> CheckDownloaded(string folder, long mediaId, string apiType) =>
        Task.FromResult(CheckDownloadedResult);

    public Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid,
        bool isArchived, DateTime createdAt, long userId) => throw new NotImplementedException();

    public Task AddPost(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
        DateTime createdAt) => throw new NotImplementedException();

    public Task AddStory(string folder, long postId, string messageText, string price, bool isPaid, bool isArchived,
        DateTime createdAt) => throw new NotImplementedException();

    public Task CreateDb(string folder) => throw new NotImplementedException();

    public Task CreateUsersDb(Dictionary<string, long> users) => throw new NotImplementedException();

    public Task CheckUsername(KeyValuePair<string, long> user, string path) => throw new NotImplementedException();

    public Task AddMedia(string folder, long mediaId, long postId, string link, string? directory,
        string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded,
        DateTime? createdAt) => throw new NotImplementedException();

    public Task<DateTime?> GetMostRecentPostDate(string folder) => throw new NotImplementedException();
}

internal sealed class StaticApiService : IApiService
{
    public Dictionary<long, string>? MediaToReturn { get; init; }

    public DateTime LastModifiedToReturn { get; set; } = new(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    public bool OfdlCalled { get; private set; }

    public bool CdmCalled { get; private set; }

    public Task<string> GetDrmMpdPssh(string mpdUrl, string policy, string signature, string kvp) =>
        Task.FromResult("pssh");

    public Task<DateTime> GetDrmMpdLastModified(string mpdUrl, string policy, string signature, string kvp) =>
        Task.FromResult(LastModifiedToReturn);

    public Task<string> GetDecryptionKeyOfdl(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh)
    {
        OfdlCalled = true;
        return Task.FromResult("ofdl-key");
    }

    public Task<string> GetDecryptionKeyCdm(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh)
    {
        CdmCalled = true;
        return Task.FromResult("cdm-key");
    }

    public Dictionary<string, string> GetDynamicHeaders(string path, string queryParam) =>
        new() { { "X-Test", "value" } };

    public Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username,
        string folder) => Task.FromResult(MediaToReturn);

    public Task<Dictionary<string, long>?> GetLists(string endpoint) => throw new NotImplementedException();

    public Task<List<string>?> GetListUsers(string endpoint) => throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Purchased.PaidPostCollection> GetPaidPosts(string endpoint, string folder,
        string username, List<long> paidPostIds, IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Posts.PostCollection> GetPosts(string endpoint, string folder,
        List<long> paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Posts.SinglePostCollection> GetPost(string endpoint, string folder) =>
        throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Streams.StreamsCollection> GetStreams(string endpoint, string folder,
        List<long> paidPostIds, IStatusReporter statusReporter) => throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Archived.ArchivedCollection> GetArchived(string endpoint, string folder,
        IStatusReporter statusReporter) => throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Messages.MessageCollection> GetMessages(string endpoint, string folder,
        IStatusReporter statusReporter) => throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Purchased.PaidMessageCollection> GetPaidMessages(string endpoint,
        string folder, string username, IStatusReporter statusReporter) => throw new NotImplementedException();

    public Task<OF_DL.Models.Entities.Purchased.SinglePaidMessageCollection> GetPaidMessage(string endpoint,
        string folder) => throw new NotImplementedException();

    public Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users) =>
        throw new NotImplementedException();

    public Task<List<OF_DL.Models.Entities.Purchased.PurchasedTabCollection>> GetPurchasedTab(string endpoint,
        string folder, Dictionary<string, long> users) => throw new NotImplementedException();

    public Task<UserEntities.User?> GetUserInfo(string endpoint) =>
        throw new NotImplementedException();

    public Task<JObject?> GetUserInfoById(string endpoint) =>
        throw new NotImplementedException();

    public Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint,
        bool includeRestrictedSubscriptions) => throw new NotImplementedException();

    public Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint,
        bool includeRestrictedSubscriptions) => throw new NotImplementedException();
}

internal sealed class ConfigurableApiService : IApiService
{
    public Func<string, bool, Task<Dictionary<string, long>?>>? ActiveSubscriptionsHandler { get; init; }
    public Func<string, bool, Task<Dictionary<string, long>?>>? ExpiredSubscriptionsHandler { get; init; }
    public Func<string, Task<Dictionary<string, long>?>>? ListsHandler { get; init; }
    public Func<string, Task<List<string>?>>? ListUsersHandler { get; init; }
    public Func<MediaType, string, string?, string, Task<Dictionary<long, string>?>>? MediaHandler { get; init; }
    public Func<string, string, Task<PostEntities.SinglePostCollection>>? PostHandler { get; init; }
    public Func<string, string, Task<PurchasedEntities.SinglePaidMessageCollection>>? PaidMessageHandler { get; init; }
    public Func<string, Task<UserEntities.User?>>? UserInfoHandler { get; init; }
    public Func<string, Task<JObject?>>? UserInfoByIdHandler { get; init; }

    public Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint,
        bool includeRestrictedSubscriptions) =>
        ActiveSubscriptionsHandler?.Invoke(endpoint, includeRestrictedSubscriptions) ??
        Task.FromResult<Dictionary<string, long>?>(null);

    public Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint,
        bool includeRestrictedSubscriptions) =>
        ExpiredSubscriptionsHandler?.Invoke(endpoint, includeRestrictedSubscriptions) ??
        Task.FromResult<Dictionary<string, long>?>(null);

    public Task<Dictionary<string, long>?> GetLists(string endpoint) =>
        ListsHandler?.Invoke(endpoint) ?? Task.FromResult<Dictionary<string, long>?>(null);

    public Task<List<string>?> GetListUsers(string endpoint) =>
        ListUsersHandler?.Invoke(endpoint) ?? Task.FromResult<List<string>?>(null);

    public Task<Dictionary<long, string>?> GetMedia(MediaType mediaType, string endpoint, string? username,
        string folder) =>
        MediaHandler?.Invoke(mediaType, endpoint, username, folder) ??
        Task.FromResult<Dictionary<long, string>?>(null);

    public Task<PostEntities.SinglePostCollection> GetPost(string endpoint, string folder) =>
        PostHandler?.Invoke(endpoint, folder) ?? Task.FromResult(new PostEntities.SinglePostCollection());

    public Task<PurchasedEntities.SinglePaidMessageCollection> GetPaidMessage(string endpoint, string folder) =>
        PaidMessageHandler?.Invoke(endpoint, folder) ??
        Task.FromResult(new PurchasedEntities.SinglePaidMessageCollection());

    public Task<UserEntities.User?> GetUserInfo(string endpoint) =>
        UserInfoHandler?.Invoke(endpoint) ?? Task.FromResult<UserEntities.User?>(null);

    public Task<JObject?> GetUserInfoById(string endpoint) =>
        UserInfoByIdHandler?.Invoke(endpoint) ?? Task.FromResult<JObject?>(null);

    public Task<PurchasedEntities.PaidPostCollection> GetPaidPosts(string endpoint, string folder, string username,
        List<long> paidPostIds, IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<PostEntities.PostCollection> GetPosts(string endpoint, string folder, List<long> paidPostIds,
        IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<StreamEntities.StreamsCollection> GetStreams(string endpoint, string folder, List<long> paidPostIds,
        IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<ArchivedEntities.ArchivedCollection> GetArchived(string endpoint, string folder,
        IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder,
        IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<PurchasedEntities.PaidMessageCollection> GetPaidMessages(string endpoint, string folder,
        string username, IStatusReporter statusReporter) =>
        throw new NotImplementedException();

    public Task<Dictionary<string, long>> GetPurchasedTabUsers(string endpoint, Dictionary<string, long> users) =>
        throw new NotImplementedException();

    public Task<List<PurchasedEntities.PurchasedTabCollection>> GetPurchasedTab(string endpoint, string folder,
        Dictionary<string, long> users) =>
        throw new NotImplementedException();

    public Dictionary<string, string> GetDynamicHeaders(string path, string queryParam) =>
        throw new NotImplementedException();

    public Task<string> GetDecryptionKeyCdm(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh) =>
        throw new NotImplementedException();

    public Task<DateTime> GetDrmMpdLastModified(string mpdUrl, string policy, string signature, string kvp) =>
        throw new NotImplementedException();

    public Task<string> GetDrmMpdPssh(string mpdUrl, string policy, string signature, string kvp) =>
        throw new NotImplementedException();

    public Task<string> GetDecryptionKeyOfdl(Dictionary<string, string> drmHeaders, string licenceUrl, string pssh) =>
        throw new NotImplementedException();
}

internal sealed class OrchestrationDownloadServiceStub : IDownloadService
{
    public bool SinglePostCalled { get; private set; }
    public bool SinglePaidMessageCalled { get; private set; }

    public DownloadResult? SinglePostResult { get; init; }
    public DownloadResult? SinglePaidMessageResult { get; init; }
    public DownloadResult? StoriesResult { get; init; }

    public Task<long> CalculateTotalFileSize(List<string> urls) => Task.FromResult((long)urls.Count);

    public Task<bool> ProcessMediaDownload(string folder, long mediaId, string apiType, string url, string path,
        string serverFileName, string resolvedFileName, string extension, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<(string decryptionKey, DateTime lastModified)?> GetDecryptionInfo(string mpdUrl, string policy,
        string signature, string kvp, string mediaId, string contentId, string drmType, bool clientIdBlobMissing,
        bool devicePrivateKeyMissing) =>
        throw new NotImplementedException();

    public Task DownloadAvatarHeader(string? avatarUrl, string? headerUrl, string folder, string username) =>
        Task.CompletedTask;

    public Task<DownloadResult> DownloadHighlights(string username, long userId, string path,
        HashSet<long> paidPostIds, IProgressReporter progressReporter) =>
        Task.FromResult(new DownloadResult());

    public Task<DownloadResult> DownloadStories(string username, long userId, string path,
        HashSet<long> paidPostIds, IProgressReporter progressReporter) =>
        Task.FromResult(StoriesResult ?? new DownloadResult());

    public Task<DownloadResult> DownloadArchived(string username, long userId, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        ArchivedEntities.ArchivedCollection archived, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadMessages(string username, long userId, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        MessageEntities.MessageCollection messages, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadPaidMessages(string username, string path, Dictionary<string, long> users,
        bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadStreams(string username, long userId, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        StreamEntities.StreamsCollection streams, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadFreePosts(string username, long userId, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        PostEntities.PostCollection posts, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadPaidPosts(string username, long userId, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadPaidPostsPurchasedTab(string username, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        PurchasedEntities.PaidPostCollection purchasedPosts, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadPaidMessagesPurchasedTab(string username, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        PurchasedEntities.PaidMessageCollection paidMessageCollection, IProgressReporter progressReporter) =>
        throw new NotImplementedException();

    public Task<DownloadResult> DownloadSinglePost(string username, string path, Dictionary<string, long> users,
        bool clientIdBlobMissing, bool devicePrivateKeyMissing, PostEntities.SinglePostCollection post,
        IProgressReporter progressReporter)
    {
        SinglePostCalled = true;
        return Task.FromResult(SinglePostResult ?? new DownloadResult());
    }

    public Task<DownloadResult> DownloadSinglePaidMessage(string username, string path,
        Dictionary<string, long> users, bool clientIdBlobMissing, bool devicePrivateKeyMissing,
        PurchasedEntities.SinglePaidMessageCollection singlePaidMessageCollection,
        IProgressReporter progressReporter)
    {
        SinglePaidMessageCalled = true;
        return Task.FromResult(SinglePaidMessageResult ?? new DownloadResult());
    }
}

internal sealed class UserTrackingDbService : IDbService
{
    public Dictionary<string, long>? CreatedUsers { get; private set; }
    public List<string> CreatedDbs { get; } = [];
    public (KeyValuePair<string, long> user, string path)? CheckedUser { get; private set; }

    public Task CreateDb(string folder)
    {
        CreatedDbs.Add(folder);
        return Task.CompletedTask;
    }

    public Task CreateUsersDb(Dictionary<string, long> users)
    {
        CreatedUsers = new Dictionary<string, long>(users);
        return Task.CompletedTask;
    }

    public Task CheckUsername(KeyValuePair<string, long> user, string path)
    {
        CheckedUser = (user, path);
        return Task.CompletedTask;
    }

    public Task AddMessage(string folder, long postId, string messageText, string price, bool isPaid,
        bool isArchived, DateTime createdAt, long userId) => throw new NotImplementedException();

    public Task AddPost(string folder, long postId, string messageText, string price, bool isPaid,
        bool isArchived, DateTime createdAt) => throw new NotImplementedException();

    public Task AddStory(string folder, long postId, string messageText, string price, bool isPaid,
        bool isArchived, DateTime createdAt) => throw new NotImplementedException();

    public Task AddMedia(string folder, long mediaId, long postId, string link, string? directory,
        string? filename, long? size, string apiType, string mediaType, bool preview, bool downloaded,
        DateTime? createdAt) => throw new NotImplementedException();

    public Task UpdateMedia(string folder, long mediaId, string apiType, string directory, string filename,
        long size, bool downloaded, DateTime createdAt) => throw new NotImplementedException();

    public Task<long> GetStoredFileSize(string folder, long mediaId, string apiType) =>
        throw new NotImplementedException();

    public Task<bool> CheckDownloaded(string folder, long mediaId, string apiType) =>
        throw new NotImplementedException();

    public Task<DateTime?> GetMostRecentPostDate(string folder) => throw new NotImplementedException();
}

internal sealed class RecordingDownloadEventHandler : IDownloadEventHandler
{
    public List<string> Messages { get; } = [];
    public List<(string contentType, int mediaCount, int objectCount)> ContentFound { get; } = [];
    public List<string> NoContent { get; } = [];
    public List<(string contentType, DownloadResult result)> DownloadCompletes { get; } = [];
    public List<(string description, long maxValue, bool showSize)> ProgressCalls { get; } = [];

    public Task<T> WithStatusAsync<T>(string statusMessage, Func<IStatusReporter, Task<T>> work) =>
        work(new RecordingStatusReporter(statusMessage));

    public Task<T> WithProgressAsync<T>(string description, long maxValue, bool showSize,
        Func<IProgressReporter, Task<T>> work)
    {
        ProgressCalls.Add((description, maxValue, showSize));
        return work(new ProgressRecorder());
    }

    public void OnContentFound(string contentType, int mediaCount, int objectCount) =>
        ContentFound.Add((contentType, mediaCount, objectCount));

    public void OnNoContentFound(string contentType) => NoContent.Add(contentType);

    public void OnDownloadComplete(string contentType, DownloadResult result) =>
        DownloadCompletes.Add((contentType, result));

    public void OnUserStarting(string username) => Messages.Add($"Starting {username}");

    public void OnUserComplete(string username, CreatorDownloadResult result) =>
        Messages.Add($"Completed {username}");

    public void OnPurchasedTabUserComplete(string username, int paidPostCount, int paidMessagesCount) =>
        Messages.Add($"Purchased {username}");

    public void OnScrapeComplete(TimeSpan elapsed) => Messages.Add("Scrape complete");

    public void OnMessage(string message) => Messages.Add(message);
}

internal sealed class RecordingStatusReporter : IStatusReporter
{
    private readonly List<string> _statuses;

    public RecordingStatusReporter(string initialStatus)
    {
        _statuses = [initialStatus];
    }

    public IReadOnlyList<string> Statuses => _statuses;

    public void ReportStatus(string message) => _statuses.Add(message);
}

internal sealed class FakeFileNameService : IFileNameService
{
    public Task<string> BuildFilename(string fileFormat, Dictionary<string, string> values) =>
        throw new NotImplementedException();

    public Task<Dictionary<string, string>> GetFilename(object info, object media, object author,
        List<string> selectedProperties, string username, Dictionary<string, long>? users = null) =>
        throw new NotImplementedException();
}

internal sealed class FakeAuthService : IAuthService
{
    public Auth? CurrentAuth { get; set; }

    public Task<bool> LoadFromFileAsync(string filePath = "auth.json") => throw new NotImplementedException();

    public Task<bool> LoadFromBrowserAsync() => throw new NotImplementedException();

    public Task SaveToFileAsync(string filePath = "auth.json") => throw new NotImplementedException();

    public void ValidateCookieString() => throw new NotImplementedException();

    public Task<UserEntities.User?> ValidateAuthAsync() => throw new NotImplementedException();

    public void Logout() => throw new NotImplementedException();
}

internal sealed class FakeLoggingService : ILoggingService
{
    public LoggingLevelSwitch LevelSwitch { get; } = new();
    public LoggingLevel LastLevel { get; private set; } = LoggingLevel.Error;
    public int UpdateCount { get; private set; }

    public void UpdateLoggingLevel(LoggingLevel newLevel)
    {
        UpdateCount++;
        LastLevel = newLevel;
        LevelSwitch.MinimumLevel = (LogEventLevel)newLevel;
    }

    public LoggingLevel GetCurrentLoggingLevel() => (LoggingLevel)LevelSwitch.MinimumLevel;
}
