namespace OF_DL.Services;

public class CajetanApiService(IAuthService authService, IConfigService configService, ICajetanDbService dbService, ICajetanDownloadEventHandler eventHandler)
    : ApiService(authService, configService, dbService), ICajetanApiService
{
    private readonly ICajetanDownloadEventHandler _eventHandler = eventHandler;

    public new async Task<UserEntities.User?> GetUserInfo(string endpoint)
    {
        UserEntities.UserInfo? userInfo = await GetDetailedUserInfoAsync(endpoint);

        if (userInfo is not null && !endpoint.EndsWith("/me"))
            await dbService.UpdateUserInfoAsync(userInfo);

        return userInfo;
    }

    public new Task<Dictionary<string, long>?> GetActiveSubscriptions(string endpoint, bool includeRestricted)
    {
        Log.Debug("Calling GetActiveSubscriptions");
        return GetAllSubscriptions(endpoint, includeRestricted, "active");
    }

    public new Task<Dictionary<string, long>?> GetExpiredSubscriptions(string endpoint, bool includeRestricted)
    {
        Log.Debug("Calling GetExpiredSubscriptions");
        return GetAllSubscriptions(endpoint, includeRestricted, "expired");
    }

    public new async Task<MessageEntities.MessageCollection> GetMessages(string endpoint, string folder, IStatusReporter statusReporter)
    {
        (bool couldExtract, long userId) = ExtractUserId(endpoint);

        _eventHandler.OnMessage("Getting Unread Chats", "grey");
        HashSet<long> usersWithUnread = couldExtract ? await GetUsersWithUnreadMessagesAsync() : [];

        MessageEntities.MessageCollection messages = await base.GetMessages(endpoint, folder, statusReporter);

        if (usersWithUnread.Contains(userId))
        {
            _eventHandler.OnMessage("Restoring unread state", "grey");
            await MarkAsUnreadAsync($"/chats/{userId}/mark-as-read");
        }

        return messages;

        static (bool couldExtract, long userId) ExtractUserId(string endpoint)
        {
            string withoutChatsAndMessages = endpoint
                .Replace("chats", "", StringComparison.OrdinalIgnoreCase)
                .Replace("messages", "", StringComparison.OrdinalIgnoreCase);
            string trimmed = withoutChatsAndMessages.Trim(' ', '/', '\\');

            if (long.TryParse(trimmed, out long userId))
                return (true, userId);

            return (false, default);
        }
    }

    public new async Task<Dictionary<string, long>?> GetListUsers(string endpoint)
    {
        if (!HasSignedRequestAuth())
            return null;

        try
        {
            Dictionary<string, long> users = new(StringComparer.OrdinalIgnoreCase);

            Log.Debug($"Calling GetListUsers - {endpoint}");

            const int limit = 50;
            int offset = 0;

            Dictionary<string, string> getParams = new()
            {
                ["format"] = "infinite",
                ["limit"] = limit.ToString()
            };

            while (true)
            {
                getParams["offset"] = offset.ToString();

                string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());

                if (string.IsNullOrWhiteSpace(body))
                    break;

                ListsDtos.ListUsersDto? listUsers = DeserializeJson<ListsDtos.ListUsersDto>(body, s_mJsonSerializerSettings);

                if (listUsers?.List is null)
                    break;

                foreach (ListsDtos.UsersListDto item in listUsers.List)
                {
                    if (item.Id is null)
                        continue;

                    users.TryAdd(item.Username, item.Id.Value);
                }

                if (listUsers.HasMore is false)
                    break;

                offset = listUsers.NextOffset;
            }

            return users;
        }
        catch (Exception ex)
        {
            ExceptionLoggerHelper.LogException(ex);
        }

        return null;
    }

    public async Task<UserEntities.UserInfo?> GetDetailedUserInfoAsync(string endpoint)
    {
        Log.Debug($"Calling GetDetailedUserInfo: {endpoint}");

        if (!HasSignedRequestAuth())
            return null;

        try
        {
            UserEntities.UserInfo userInfo = new();
            Dictionary<string, string> getParams = new()
            {
                { "limit", Constants.ApiPageSize.ToString() }, { "order", "publish_date_asc" }
            };

            HttpClient client = new();
            HttpRequestMessage request = await BuildHttpRequestMessage(getParams, endpoint);

            using HttpResponseMessage response = await client.SendAsync(request);

            if (!response.IsSuccessStatusCode)
                return userInfo;

            response.EnsureSuccessStatusCode();
            string body = await response.Content.ReadAsStringAsync();
            UserDtos.UserDto? userDto = JsonConvert.DeserializeObject<UserDtos.UserDto>(body, s_mJsonSerializerSettings);
            userInfo = FromDto(userDto);
            return userInfo;
        }
        catch (Exception ex)
        {
            ExceptionLoggerHelper.LogException(ex);
        }

        return null;
    }

    public async Task<Dictionary<string, long>> GetUsersWithProgressAsync(string typeDisplay, string endpoint, string? typeParam, bool offsetByCount)
    {
        Dictionary<string, long> usersOfType = await _eventHandler.WithStatusAsync(
            statusMessage: $"Getting {typeDisplay} Users",
            work: FetchAsync
        );

        return usersOfType;

        async Task<Dictionary<string, long>> FetchAsync(IStatusReporter statusReporter)
        {
            Dictionary<string, long> users = [];

            int limit = 50;
            int offset = 0;
            bool includeRestricted = true;

            Dictionary<string, string> getParams = new()
            {
                ["format"] = "infinite",
                ["limit"] = limit.ToString(),
                ["offset"] = offset.ToString()
            };

            if (!string.IsNullOrWhiteSpace(typeParam))
                getParams["type"] = typeParam;

            try
            {
                Log.Debug("Calling GetUsersWithProgress");

                HttpClient client = GetHttpClient();

                bool isLastLoop = false;
                while (true)
                {
                    string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, client);

                    if (string.IsNullOrWhiteSpace(body))
                        break;

                    SubscriptionsDtos.SubscriptionsDto? subscriptions = DeserializeJson<SubscriptionsDtos.SubscriptionsDto>(body, s_mJsonSerializerSettings);

                    if (subscriptions?.List is null)
                        break;

                    foreach (SubscriptionsDtos.ListItemDto item in subscriptions.List)
                    {
                        if (string.IsNullOrWhiteSpace(item?.Username))
                            continue;

                        if (users.ContainsKey(item.Username))
                            continue;

                        bool isRestricted = item.IsRestricted ?? false;
                        bool isRestrictedButAllowed = isRestricted && includeRestricted;

                        if (!isRestricted || isRestrictedButAllowed)
                            users.Add(item.Username, item.Id);
                    }

                    statusReporter.ReportStatus($"[blue]Getting {typeDisplay} Users\n[/] [blue]Found {users.Count}[/]");

                    if (isLastLoop)
                        break;

                    if (!subscriptions.HasMore || subscriptions.List.Count == 0)
                        isLastLoop = true;

                    offset += offsetByCount
                        ? subscriptions.List.Count
                        : limit;

                    getParams["offset"] = offset.ToString();
                }
            }
            catch (Exception ex)
            {
                ExceptionLoggerHelper.LogException(ex);
            }

            return users;
        }
    }

    public async Task<HashSet<long>> GetUsersWithUnreadMessagesAsync()
    {
        MessageDtos.ChatsDto unreadChats = await GetChatsAsync("/chats", onlyUnread: true);
        HashSet<long> userWithUnread = [];

        foreach (MessageDtos.ChatItemDto chatItem in unreadChats.List)
        {
            if (chatItem?.WithUser?.Id is null)
                continue;

            if (chatItem.UnreadMessagesCount <= 0)
                continue;

            userWithUnread.Add(chatItem.WithUser.Id);
        }

        return userWithUnread;
    }

    public async Task MarkAsUnreadAsync(string endpoint)
    {
        Log.Debug($"Calling MarkAsUnread - {endpoint}");

        try
        {
            var result = new { success = false };

            string? body = await BuildHeaderAndExecuteRequests([], endpoint, GetHttpClient(), HttpMethod.Delete);

            if (!string.IsNullOrWhiteSpace(body))
                result = JsonConvert.DeserializeAnonymousType(body, result);

            if (result?.success != true)
                _eventHandler.OnMessage($"Failed to mark chat as unread! Endpoint: {endpoint}", "yellow");
        }
        catch (Exception ex)
        {
            ExceptionLoggerHelper.LogException(ex);
        }
    }

    public async Task SortBlockedAsync(string endpoint, string order = "recent", string direction = "desc")
    {
        Log.Debug($"Calling SortBlocked - {endpoint}");

        try
        {
            var reqBody = new { order, direction };
            var result = new { success = false, canAddFriends = false };

            string? body = await BuildHeaderAndExecuteRequests([], endpoint, GetHttpClient(), HttpMethod.Post, reqBody);

            if (!string.IsNullOrWhiteSpace(body))
                result = JsonConvert.DeserializeAnonymousType(body, result);

            if (result?.success != true)
                _eventHandler.OnMessage($"Failed to sort blocked (order: {order}, direction; {direction})! Endpoint: {endpoint}", "yellow");
        }
        catch (Exception ex)
        {
            ExceptionLoggerHelper.LogException(ex);
        }
    }

    private async Task<Dictionary<string, long>?> GetAllSubscriptions(string endpoint, bool includeRestricted, string type)
    {
        if (!HasSignedRequestAuth())
            return null;

        try
        {
            Dictionary<string, long> users = new(StringComparer.OrdinalIgnoreCase);

            Log.Debug("Calling GetAllSubscrptions");

            const int limit = 50;
            int offset = 0;

            Dictionary<string, string> getParams = new()
            {
                ["type"] = type,
                ["format"] = "infinite",
                ["limit"] = limit.ToString(),
            };

            while (true)
            {
                getParams["offset"] = offset.ToString();

                string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, new HttpClient());

                if (string.IsNullOrWhiteSpace(body))
                    break;

                SubscriptionsDtos.SubscriptionsDto? subscriptionsDto = DeserializeJson<SubscriptionsDtos.SubscriptionsDto>(body, s_mJsonSerializerSettings);

                if (subscriptionsDto?.List is null)
                    break;

                foreach (SubscriptionsDtos.ListItemDto item in subscriptionsDto.List)
                {
                    if (string.IsNullOrWhiteSpace(item.Username))
                    {
                        Log.Warning("Found '{Type:l}' subscription user with empty username (id: {Id})", type, item.Id);
                        continue;
                    }

                    if (users.ContainsKey(item.Username))
                        continue;

                    bool isRestricted = item.IsRestricted ?? false;
                    bool isRestrictedButAllowed = isRestricted && includeRestricted;

                    if (!isRestricted || isRestrictedButAllowed)
                        users.Add(item.Username, item.Id);
                }

                if (subscriptionsDto.HasMore is false)
                    break;

                offset += limit;
            }

            return users;
        }
        catch (Exception ex)
        {
            ExceptionLoggerHelper.LogException(ex);
        }

        return null;
    }

    private async Task<MessageDtos.ChatsDto> GetChatsAsync(string endpoint, bool onlyUnread)
    {
        Log.Debug($"Calling GetChats - {endpoint}");

        MessageDtos.ChatsDto allChats = new();

        try
        {
            const int limit = 60;
            int offset = 0;

            Dictionary<string, string> getParams = new()
            {
                ["order"] = "recent",
                ["skip_users"] = "all",
                ["filter"] = "unread",
                ["limit"] = $"{limit}",
            };

            if (onlyUnread is false)
                getParams.Remove("filter");

            while (true)
            {
                getParams["offset"] = $"{offset}";

                string? body = await BuildHeaderAndExecuteRequests(getParams, endpoint, GetHttpClient());

                if (string.IsNullOrWhiteSpace(body))
                    break;

                MessageDtos.ChatsDto? chats = DeserializeJson<MessageDtos.ChatsDto>(body, s_mJsonSerializerSettings);

                if (chats?.List is null)
                    break;

                allChats.List.AddRange(chats.List);

                if (chats.HasMore is false)
                    break;

                offset = chats.NextOffset;
            }
        }
        catch (Exception ex)
        {
            ExceptionLoggerHelper.LogException(ex);
        }

        return allChats;
    }

    private static UserEntities.UserInfo FromDto(UserDtos.UserDto? userDto)
    {
        if (userDto is null)
            return new();

        return new()
        {
            Id = userDto.Id,

            Avatar = userDto.Avatar,
            Header = userDto.Header,
            Name = userDto.Name,
            Username = userDto.Username,

            SubscribePrice = userDto.SubscribePrice,
            CurrentSubscribePrice = userDto.CurrentSubscribePrice,
            IsPaywallRequired = userDto.IsPaywallRequired,
            IsRestricted = userDto.IsRestricted,
            SubscribedBy = userDto.SubscribedBy,
            SubscribedByExpire = userDto.SubscribedByExpire,
            SubscribedByExpireDate = userDto.SubscribedByExpireDate,
            SubscribedByAutoprolong = userDto.SubscribedByAutoprolong,
            SubscribedIsExpiredNow = userDto.SubscribedIsExpiredNow,
            SubscribedOn = userDto.SubscribedOn,
            SubscribedOnExpiredNow = userDto.SubscribedOnExpiredNow,
            SubscribedOnDuration = userDto.SubscribedOnDuration,
            About = userDto.About,
            PostsCount = userDto.PostsCount,
            ArchivedPostsCount = userDto.ArchivedPostsCount,
            PrivateArchivedPostsCount = userDto.PrivateArchivedPostsCount,
            PhotosCount = userDto.PhotosCount,
            VideosCount = userDto.VideosCount,
            AudiosCount = userDto.AudiosCount,
            MediasCount = userDto.MediasCount,
        };
    }
}
