using DuoVia.FuzzyStrings;
using NTDLS.SqliteDapperWrapper;
using ZelWiki.Caching;
using ZelWiki.Engine.Library;
using ZelWiki.Library;
using ZelWiki.Models.DataModels;

namespace ZelWiki.Repository
{
    public static class PageRepository
    {
        public static Page? GetPageRevisionInfoById(int pageId, int? revision = null)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            return ManagedDataStorage.Pages.QuerySingleOrDefault<Page>("GetPageRevisionInfoById.sql", param);
        }

        public static ProcessingInstructionCollection GetPageProcessingInstructionsByPageId(int pageId, bool allowCache = true)
        {
            if (allowCache)
            {
                var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [pageId]);
                if (!WikiCache.TryGet<ProcessingInstructionCollection>(cacheKey, out var result))
                {
                    result = GetPageProcessingInstructionsByPageId(pageId, false);
                    WikiCache.Put(cacheKey, result);
                }

                return result;
            }

            var param = new
            {
                PageId = pageId
            };

            return new ProcessingInstructionCollection()
            {
                Collection = ManagedDataStorage.Pages.Query<ProcessingInstruction>("GetPageProcessingInstructionsByPageId.sql", param).ToList()
            };
        }

        public static List<PageTag> GetPageTagsById(int pageId, bool allowCache = true)
        {
            if (allowCache)
            {
                var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [pageId]);
                if (!WikiCache.TryGet<List<PageTag>>(cacheKey, out var result))
                {
                    result = GetPageTagsById(pageId, false);
                    WikiCache.Put(cacheKey, result);
                }

                return result;
            }

            var param = new
            {
                PageId = pageId
            };

            return ManagedDataStorage.Pages.Query<PageTag>("GetPageTagsById.sql", param).ToList();
        }

        public static List<PageRevision> GetPageRevisionsInfoByNavigationPaged(
            string navigation, int pageNumber, string? orderBy = null, string? orderByDirection = null, int? pageSize = null)
        {
            pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                Navigation = navigation,
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");

                var query = RepositoryHelper.TransposeOrderby("GetPageRevisionsInfoByNavigationPaged.sql", orderBy, orderByDirection);
                return o.Query<PageRevision>(query, param).ToList();
            });
        }

        public static List<PageRevision> GetTopRecentlyModifiedPagesInfoByUserId(Guid userId, int topCount)
        {
            var param = new
            {
                UserId = userId,
                TopCount = topCount
            };

            return ManagedDataStorage.Pages.Query<PageRevision>("GetTopRecentlyModifiedPagesInfoByUserId.sql", param).ToList();
        }

        public static string? GetPageNavigationByPageId(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            return ManagedDataStorage.Pages.ExecuteScalar<string>("GetPageNavigationByPageId.sql", param);
        }

        public static List<Page> GetTopRecentlyModifiedPagesInfo(int topCount)
        {
            var param = new
            {
                TopCount = topCount
            };

            return ManagedDataStorage.Pages.Query<Page>("GetTopRecentlyModifiedPagesInfo.sql", param).ToList();
        }

        private static List<PageSearchToken> GetFuzzyPageSearchTokens(List<PageToken> tokens, double minimumMatchScore)
        {
            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    MinimumMatchScore = minimumMatchScore,
                    TokenCount = tokens.Count
                };

                using var tempTable = o.CreateTempTableFrom("TempSearchTerms", tokens.Distinct());
                return o.Query<PageSearchToken>("GetFuzzyPageSearchTokens.sql", param).ToList();
            });
        }

        private static List<PageSearchToken> GetExactPageSearchTokens(List<PageToken> tokens, double minimumMatchScore)
        {
            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    MinimumMatchScore = minimumMatchScore,
                    TokenCount = tokens.Count
                };

                using var tempTable = o.CreateTempTableFrom("TempSearchTerms", tokens.Distinct());
                return o.Query<PageSearchToken>("GetExactPageSearchTokens.sql", param).ToList();
            });
        }

        private static List<PageSearchToken> GetMeteredPageSearchTokens(List<string> searchTerms, bool allowFuzzyMatching, bool allowCache = true)
        {
            if (allowCache)
            {
                //This caching is really just used for paging - so we don't have to do a token search for every click of next/previous.
                var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Search, [string.Join(',', searchTerms), allowFuzzyMatching]);
                if (!WikiCache.TryGet<List<PageSearchToken>>(cacheKey, out var result))
                {
                    result = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching, false);
                    WikiCache.Put(cacheKey, result);
                }

                return result;
            }

            var minimumMatchScore = ConfigurationRepository.Get<float>("Search", "Minimum Match Score");

            var searchTokens = (from o in searchTerms
                                select new PageToken
                                {
                                    Token = o,
                                    DoubleMetaphone = o.ToDoubleMetaphone()
                                }).ToList();

            if (allowFuzzyMatching == true)
            {
                var allTokens = GetExactPageSearchTokens(searchTokens, minimumMatchScore / 2.0);
                var fuzzyTokens = GetFuzzyPageSearchTokens(searchTokens, minimumMatchScore / 2.0);

                allTokens.AddRange(fuzzyTokens);

                return allTokens
                        .GroupBy(token => token.PageId)
                        .Where(group => group.Sum(g => g.Score) >= minimumMatchScore) // Filtering groups
                        .Select(group => new PageSearchToken
                        {
                            PageId = group.Key,
                            Match = group.Max(g => g.Match),
                            Weight = group.Max(g => g.Weight),
                            Score = group.Max(g => g.Score)
                        }).ToList();
            }
            else
            {
                return GetExactPageSearchTokens(searchTokens, minimumMatchScore / 2.0);
            }
        }

        public static List<Page> PageSearch(List<string> searchTerms)
        {
            if (searchTerms.Count == 0)
            {
                return new List<Page>();
            }

            bool allowFuzzyMatching = ConfigurationRepository.Get<bool>("Search", "Allow Fuzzy Matching");
            var meteredSearchTokens = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching == true);
            if (meteredSearchTokens.Count == 0)
            {
                return new List<Page>();
            }

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    MaximumScore = meteredSearchTokens.Max(t => t.Score)
                };

                using var users_db = o.Attach("users.db", "users_db");
                using var tempTable = o.CreateTempTableFrom("TempSearchTerms", meteredSearchTokens);
                return o.Query<Page>("PageSearch.sql", param).ToList();
            });
        }

        public static List<Page> PageSearchPaged(List<string> searchTerms, int pageNumber, int? pageSize = null, bool? allowFuzzyMatching = null)
        {
            if (searchTerms.Count == 0)
            {
                return new List<Page>();
            }

            pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size");
            allowFuzzyMatching ??= ConfigurationRepository.Get<bool>("Search", "Allow Fuzzy Matching");

            var meteredSearchTokens = GetMeteredPageSearchTokens(searchTerms, allowFuzzyMatching == true);
            if (meteredSearchTokens.Count == 0)
            {
                return new List<Page>();
            }

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    PageNumber = pageNumber,
                    PageSize = pageSize,
                    MaximumScore = meteredSearchTokens.Max(t => t.Score)
                };

                using var users_db = o.Attach("users.db", "users_db");
                using var tempTable = o.CreateTempTableFrom("TempSearchTerms", meteredSearchTokens);
                var results = o.Query<Page>("PageSearchPaged.sql", param).ToList();
                return results;
            });
        }

        public static List<RelatedPage> GetSimilarPagesPaged(int pageId, int similarity, int pageNumber, int? pageSize = null)
        {
            pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageId = pageId,
                Similarity = similarity,
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            return ManagedDataStorage.Pages.Query<RelatedPage>("GetSimilarPagesPaged.sql", param).ToList();
        }

        public static List<RelatedPage> GetRelatedPagesPaged(int pageId, int pageNumber, int? pageSize = null)
        {
            pageSize ??= ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageId = pageId,
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            return ManagedDataStorage.Pages.Query<RelatedPage>("GetRelatedPagesPaged.sql", param).ToList();
        }

        public static void FlushPageCache(int pageId)
        {
            var pageNavigation = GetPageNavigationByPageId(pageId);
            WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.Page, [pageNavigation]));
            WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.Page, [pageId]));
        }

        public static void InsertPageComment(int pageId, Guid userId, string body)
        {
            var param = new
            {
                PageId = pageId,
                UserId = userId,
                Body = body,
                CreatedDate = DateTime.UtcNow
            };

            ManagedDataStorage.Pages.Execute("InsertPageComment.sql", param);

            FlushPageCache(pageId);
        }

        public static void DeletePageCommentById(int pageId, int commentId)
        {
            var param = new
            {
                PageId = pageId,
                CommentId = commentId
            };

            ManagedDataStorage.Pages.Execute("DeletePageCommentById.sql", param);

            FlushPageCache(pageId);
        }

        public static void DeletePageCommentByUserAndId(int pageId, Guid userId, int commentId)
        {
            var param = new
            {
                PageId = pageId,
                UserId = userId,
                CommentId = commentId
            };

            ManagedDataStorage.Pages.Execute("DeletePageCommentByUserAndId.sql", param);

            FlushPageCache(pageId);
        }

        public static List<PageComment> GetPageCommentsPaged(string navigation, int pageNumber, bool allowCache = true)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            if (allowCache)
            {
                var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [navigation, pageNumber, pageSize]);
                if (!WikiCache.TryGet<List<PageComment>>(cacheKey, out var result))
                {
                    result = GetPageCommentsPaged(navigation, pageNumber, false);
                    WikiCache.Put(cacheKey, result);
                }

                return result;
            }

            var param = new
            {
                Navigation = navigation,
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.Query<PageComment>("GetPageCommentsPaged.sql", param).ToList();
            });
        }

        public static List<NonexistentPage> GetMissingPagesPaged(int pageNumber, string? orderBy = null, string? orderByDirection = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            var query = RepositoryHelper.TransposeOrderby("GetMissingPagesPaged.sql", orderBy, orderByDirection);
            return ManagedDataStorage.Pages.Query<NonexistentPage>(query, param).ToList();
        }

        public static void UpdateSinglePageReference(string pageNavigation, int pageId)
        {
            var param = new
            {
                @PageId = pageId,
                @PageNavigation = pageNavigation
            };

            ManagedDataStorage.Pages.Execute("UpdateSinglePageReference.sql", param);

            FlushPageCache(pageId);
        }

        public static void UpdatePageReferences(int pageId, List<PageReference> referencesPageNavigations)
        {
            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    PageId = pageId
                };

                using var tempTable = o.CreateTempTableFrom("TempReferences", referencesPageNavigations.Distinct());
                return o.Query<Page>("UpdatePageReferences.sql", param).ToList();
            });

            FlushPageCache(pageId);
        }

        public static List<Page> GetAllPagesByInstructionPaged(int pageNumber, string? instruction = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageNumber = pageNumber,
                PageSize = pageSize,
                Instruction = instruction
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.Query<Page>("GetAllPagesByInstructionPaged.sql", param).ToList();
            });
        }

        public static List<int> GetDeletedPageIdsByTokens(List<string>? tokens)
        {
            if (tokens == null || tokens.Count == 0)
            {
                return new List<int>();
            }

            return ManagedDataStorage.DeletedPages.Ephemeral(o =>
            {
                var param = new
                {
                    TokenCount = tokens.Count
                };

                using var tempTable = o.CreateTempTableFrom("TempTokens", tokens);
                return o.Query<int>("GetDeletedPageIdsByTokens.sql", param).ToList();
            });
        }

        public static List<int> GetPageIdsByTokens(List<string>? tokens)
        {
            if (tokens == null || tokens.Count == 0)
            {
                return new List<int>();
            }

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    TokenCount = tokens.Count
                };

                using var tempTable = o.CreateTempTableFrom("TempTokens", tokens);
                return o.Query<int>("GetPageIdsByTokens.sql", param).ToList();
            });
        }

        public static List<Page> GetAllNamespacePagesPaged(int pageNumber, string namespaceName,
            string? orderBy = null, string? orderByDirection = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageNumber = pageNumber,
                PageSize = pageSize,
                Namespace = namespaceName
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                var query = RepositoryHelper.TransposeOrderby("GetAllNamespacePagesPaged.sql", orderBy, orderByDirection);
                return o.Query<Page>(query, param).ToList();
            });
        }

        /// <summary>
        /// Unlike the search, this method returns all pages and allows them to be paired down using the search terms.
        /// Whereas the search requires a search term to get results. The matching here is also exact, no score based matching.
        /// </summary>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="searchTerms"></param>
        /// <returns></returns>
        public static List<Page> GetAllPagesPaged(int pageNumber,
            string? orderBy = null, string? orderByDirection = null, List<string>? searchTerms = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            if (searchTerms?.Count > 0)
            {
                var pageIds = GetPageIdsByTokens(searchTerms);

                return ManagedDataStorage.Pages.Ephemeral(o =>
                {
                    using var users_db = o.Attach("users.db", "users_db");
                    using var deletedpagerevisions_db = o.Attach("deletedpagerevisions.db", "deletedpagerevisions_db");
                    using var tempTable = o.CreateTempTableFrom("TempPageIds", pageIds);

                    var query = RepositoryHelper.TransposeOrderby("GetAllPagesByPageIdPaged.sql", orderBy, orderByDirection);
                    return o.Query<Page>(query, param).ToList();
                });
            }

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                using var deletedpagerevisions_db = o.Attach("deletedpagerevisions.db", "deletedpagerevisions_db");

                var query = RepositoryHelper.TransposeOrderby("GetAllPagesPaged.sql", orderBy, orderByDirection);
                return o.Query<Page>(query, param).ToList();
            });
        }

        /// <summary>
        /// Unlike the search, this method returns all pages and allows them to be paired down using the search terms.
        /// Whereas the search requires a search term to get results. The matching here is also exact, no score based matching.
        /// </summary>
        /// <param name="pageNumber"></param>
        /// <param name="pageSize"></param>
        /// <param name="searchTerms"></param>
        /// <returns></returns>
        public static List<Page> GetAllDeletedPagesPaged(int pageNumber, string? orderBy = null,
            string? orderByDirection = null, List<string>? searchTerms = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            if (searchTerms?.Count > 0)
            {
                var pageIds = GetDeletedPageIdsByTokens(searchTerms);
                return ManagedDataStorage.DeletedPages.Ephemeral(o =>
                {
                    using var users_db = o.Attach("users.db", "users_db");
                    using var tempTable = o.CreateTempTableFrom("TempPageIds", pageIds);

                    var query = RepositoryHelper.TransposeOrderby("GetAllDeletedPagesByPageIdPaged.sql", orderBy, orderByDirection);
                    return o.Query<Page>(query, param).ToList();
                });
            }

            return ManagedDataStorage.DeletedPages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                var query = RepositoryHelper.TransposeOrderby("GetAllDeletedPagesPaged.sql", orderBy, orderByDirection);
                return o.Query<Page>(query, param).ToList();
            });
        }

        public static List<NamespaceStat> GetAllNamespacesPaged(int pageNumber, string? orderBy = null, string? orderByDirection = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            var query = RepositoryHelper.TransposeOrderby("GetAllNamespacesPaged.sql", orderBy, orderByDirection);

            return ManagedDataStorage.Pages.Query<NamespaceStat>(query, param).ToList();
        }

        public static List<string> GetAllNamespaces()
            => ManagedDataStorage.Pages.Query<string>("GetAllNamespaces.sql").ToList();

        public static List<Page> GetAllPages()
            => ManagedDataStorage.Pages.Query<Page>("GetAllPages.sql").ToList();

        public static void UpdatePageProcessingInstructions(int pageId, List<string> instructions)
        {
            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var param = new
                {
                    PageId = pageId
                };

                instructions = instructions.Select(o => o.ToLower()).Distinct().ToList();

                using var tempTable = o.CreateTempTableFrom("TempInstructions", instructions);
                return o.Query<Page>("UpdatePageProcessingInstructions.sql", param).ToList();
            });

            FlushPageCache(pageId);
        }

        public static Page? GetPageRevisionById(int pageId, int? revision = null)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            return ManagedDataStorage.Pages.QuerySingleOrDefault<Page>("GetPageRevisionById.sql", param);
        }

        public static void SavePageSearchTokens(List<PageToken> items)
        {
            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var tempTable = o.CreateTempTableFrom("TempTokens", items.Distinct());
                return o.Query<Page>("SavePageSearchTokens.sql").ToList();
            });
        }

        public static void TruncateAllPageRevisions(string confirm)
        {
            if (confirm != "YES") //Are you REALLY sure?
            {
                return;
            }
            else
            {
                ManagedDataStorage.Pages.Ephemeral(o =>
                {
                    var transaction = o.BeginTransaction();
                    try
                    {
                        o.Execute("TruncateAllPageRevisions.sql");
                        transaction.Commit();
                    }
                    catch
                    {
                        transaction.Rollback();
                        throw;
                    }
                });
            }
        }

        public static int GetCurrentPageRevision(int pageId)
        {
            var param = new
            {
                PageId = pageId,
            };

            return ManagedDataStorage.Pages.ExecuteScalar<int>("GetCurrentPageRevision.sql", param);
        }

        public static int GetCurrentPageRevision(ManagedDataStorageInstance connection, int pageId)
        {
            var param = new
            {
                PageId = pageId,
            };

            return connection.ExecuteScalar<int>("GetCurrentPageRevision.sql", param);
        }

        public static Page? GetLimitedPageInfoByIdAndRevision(int pageId, int? revision = null, bool allowCache = true)
        {
            if (allowCache)
            {
                var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [pageId, revision]);
                if (!WikiCache.TryGet<Page>(cacheKey, out var result))
                {
                    if ((result = GetLimitedPageInfoByIdAndRevision(pageId, revision, false)) != null)
                    {
                        WikiCache.Put(cacheKey, result);
                    }
                }
                return result;
            }

            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            return ManagedDataStorage.Pages.QuerySingleOrDefault<Page>("GetLimitedPageInfoByIdAndRevision.sql", param);
        }

        public static int SavePage(Page page)
        {
            var pageUpsertParam = new
            {
                PageId = page.Id,
                Name = page.Name,
                Navigation = NamespaceNavigation.CleanAndValidate(page.Name),
                Description = page.Description,
                Body = page.Body ?? string.Empty,
                Namespace = page.Namespace,
                CreatedByUserId = page.CreatedByUserId,
                CreatedDate = page.CreatedDate,
                ModifiedByUserId = page.ModifiedByUserId,
                ModifiedDate = DateTime.UtcNow
            };

            var newDataHash = Security.Helpers.Crc32(page.Body ?? string.Empty);

            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var transaction = o.BeginTransaction();

                try
                {
                    int currentPageRevision = 0;
                    bool hasPageChanged = false;

                    if (page.Id == 0)
                    {
                        //This is a new page, just insert it.
                        page.Id = o.ExecuteScalar<int>("CreatePage.sql", pageUpsertParam);
                        hasPageChanged = true;
                    }
                    else
                    {
                        //Get current page so we can determine if anything has changed.
                        var currentRevisionInfo = GetLimitedPageInfoByIdAndRevision(page.Id)
                            ?? throw new Exception("The page could not be found.");

                        currentPageRevision = currentRevisionInfo.Revision;

                        //Update the existing page.
                        o.Execute("UpdatePage.sql", pageUpsertParam);

                        //Determine if anything has actually changed.
                        hasPageChanged = currentRevisionInfo.Name != page.Name
                            || currentRevisionInfo.Namespace != page.Namespace
                            || currentRevisionInfo.Description != page.Description
                            || currentRevisionInfo.DataHash != newDataHash;
                    }

                    if (hasPageChanged)
                    {
                        currentPageRevision++;

                        var updatePageRevisionNumberParam = new
                        {
                            PageId = page.Id,
                            PageRevision = currentPageRevision
                        };
                        //The page content has actually changed (according to the checksum), so we will bump the page revision.
                        o.Execute("UpdatePageRevisionNumber.sql", updatePageRevisionNumberParam);

                        var InsertPageRevisionParam = new
                        {
                            PageId = page.Id,
                            Name = page.Name,
                            Namespace = page.Namespace,
                            Description = page.Description,
                            Body = page.Body,
                            DataHash = newDataHash,
                            PageRevision = currentPageRevision,
                            ModifiedByUserId = page.ModifiedByUserId,
                            ModifiedDate = DateTime.UtcNow,
                        };
                        //Insert the new actual page revision entry (this is the data).
                        o.Execute("InsertPageRevision.sql", InsertPageRevisionParam);

                        var reassociateAllPageAttachmentsParam = new
                        {
                            PageId = page.Id,
                            PageRevision = currentPageRevision,
                        };
                        //Associate all page attachments with the latest revision.
                        o.Execute("ReassociateAllPageAttachments.sql", reassociateAllPageAttachmentsParam);
                    }

                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            });

            return page.Id;
        }

        /// <summary>
        /// Gets the page info without the content.
        /// </summary>
        /// <param name="navigation"></param>
        /// <returns></returns>
        public static Page? GetPageInfoByNavigation(string navigation)
        {
            var param = new
            {
                Navigation = navigation
            };

            return ManagedDataStorage.Pages.QuerySingleOrDefault<Page>("GetPageInfoByNavigation.sql", param);
        }

        public static int GetPageRevisionCountByPageId(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            return ManagedDataStorage.Pages.ExecuteScalar<int>("GetPageRevisionCountByNavigation.sql", param);
        }

        public static void RestoreDeletedPageByPageId(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var transaction = o.BeginTransaction();
                try
                {
                    using var deletedpages_db = o.Attach("deletedpages.db", "deletedpages_db");
                    o.Execute("RestoreDeletedPageByPageId.sql", param);
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            });

            FlushPageCache(pageId);
        }

        public static void MovePageRevisionToDeletedById(int pageId, int revision, Guid userId)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision,
                DeletedByUserId = userId,
                DeletedDate = DateTime.UtcNow
            };

            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var transaction = o.BeginTransaction();
                try
                {
                    using var deletedpagerevisions_db = o.Attach("deletedpagerevisions.db", "deletedpagerevisions_db");
                    o.Execute("MovePageRevisionToDeletedById.sql", param);
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            });

            FlushPageCache(pageId);
        }

        public static void MovePageToDeletedById(int pageId, Guid userId)
        {
            var param = new
            {
                PageId = pageId,
                DeletedByUserId = userId,
                DeletedDate = DateTime.UtcNow
            };

            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                var transaction = o.BeginTransaction();
                try
                {
                    using var deletedpages_db = o.Attach("deletedpages.db", "deletedpages_db");

                    o.Execute("MovePageToDeletedById.sql", param);
                    transaction.Commit();
                    ManagedDataStorage.Statistics.Execute("DeletePageStatisticsByPageId.sql", param);
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            });

            FlushPageCache(pageId);
        }

        public static void PurgeDeletedPageByPageId(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            ManagedDataStorage.DeletedPages.Execute("PurgeDeletedPageByPageId.sql", param);

            PurgeDeletedPageRevisionsByPageId(pageId);

            FlushPageCache(pageId);
        }

        public static void PurgeDeletedPages()
        {
            ManagedDataStorage.DeletedPages.Execute("PurgeDeletedPages.sql");

            PurgeDeletedPageRevisions();
        }

        public static int GetCountOfPageAttachmentsById(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            return ManagedDataStorage.Pages.ExecuteScalar<int>("GetCountOfPageAttachmentsById.sql", param);
        }

        public static Page? GetDeletedPageById(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            return ManagedDataStorage.DeletedPages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.QuerySingleOrDefault<Page>("GetDeletedPageById.sql", param);
            });
        }

        public static Page? GetLatestPageRevisionById(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.QuerySingleOrDefault<Page>("GetLatestPageRevisionById.sql", param);
            });
        }

        public static int GetPageNextRevision(int pageId, int revision)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            return ManagedDataStorage.Pages.ExecuteScalar<int>("GetPageNextRevision.sql", param);
        }

        public static int GetPagePreviousRevision(int pageId, int revision)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            return ManagedDataStorage.Pages.ExecuteScalar<int>("GetPagePreviousRevision.sql", param);
        }

        public static List<DeletedPageRevision> GetDeletedPageRevisionsByIdPaged(int pageId, int pageNumber,
            string? orderBy = null, string? orderByDirection = null)
        {
            int pageSize = ConfigurationRepository.Get<int>("Customization", "Pagination Size");

            var param = new
            {
                PageId = pageId,
                PageNumber = pageNumber,
                PageSize = pageSize
            };

            return ManagedDataStorage.DeletedPageRevisions.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");

                var query = RepositoryHelper.TransposeOrderby("GetDeletedPageRevisionsByIdPaged.sql", orderBy, orderByDirection);
                return o.Query<DeletedPageRevision>(query, param).ToList();
            });
        }

        public static void PurgeDeletedPageRevisions()
        {
            ManagedDataStorage.DeletedPageRevisions.Execute("PurgeDeletedPageRevisions.sql");
        }

        public static void PurgeDeletedPageRevisionsByPageId(int pageId)
        {
            var param = new
            {
                PageId = pageId
            };

            ManagedDataStorage.DeletedPageRevisions.Execute("PurgeDeletedPageRevisionsByPageId.sql", param);

            FlushPageCache(pageId);
        }

        public static void PurgeDeletedPageRevisionByPageIdAndRevision(int pageId, int revision)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            ManagedDataStorage.DeletedPageRevisions.Execute("PurgeDeletedPageRevisionByPageIdAndRevision.sql", param);

            FlushPageCache(pageId);
        }

        public static void RestoreDeletedPageRevisionByPageIdAndRevision(int pageId, int revision)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            ManagedDataStorage.DeletedPageRevisions.Ephemeral(o =>
            {
                using var users_db = o.Attach("pages.db", "pages_db");
                o.Execute("RestoreDeletedPageRevisionByPageIdAndRevision.sql", param);
            });

            FlushPageCache(pageId);
        }

        public static DeletedPageRevision? GetDeletedPageRevisionById(int pageId, int revision)
        {
            var param = new
            {
                PageId = pageId,
                Revision = revision
            };

            return ManagedDataStorage.DeletedPageRevisions.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.Query<DeletedPageRevision>("GetDeletedPageRevisionById.sql", param).FirstOrDefault();
            });
        }

        public static Page? GetPageRevisionByNavigation(NamespaceNavigation navigation, int? revision = null)
        {
            var param = new
            {
                Navigation = navigation.Canonical,
                Revision = revision
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.QuerySingleOrDefault<Page>("GetPageRevisionByNavigation.sql", param);
            });
        }

        public static Page? GetPageRevisionByNavigation(string givenNavigation, int? revision = null, bool allowCache = true)
        {
            var navigation = new NamespaceNavigation(givenNavigation);

            if (allowCache)
            {
                var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [navigation.Canonical, revision]);
                if (!WikiCache.TryGet<Page>(cacheKey, out var result))
                {
                    if ((result = GetPageRevisionByNavigation(navigation.Canonical, revision, false)) != null)
                    {
                        WikiCache.Put(cacheKey, result);
                    }
                }

                return result;
            }

            var param = new
            {
                Navigation = navigation.Canonical,
                Revision = revision
            };

            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var users_db = o.Attach("users.db", "users_db");
                return o.QuerySingleOrDefault<Page>("GetPageRevisionByNavigation.sql", param);
            });
        }

        #region Tags.

        public static List<TagAssociation> GetAssociatedTags(string tag)
        {
            var param = new
            {
                @Tag = tag
            };

            return ManagedDataStorage.Pages.Query<TagAssociation>("GetAssociatedTags.sql", param).ToList();
        }

        public static List<Page> GetPageInfoByNamespaces(List<string> namespaces)
        {
            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var tempTable = o.CreateTempTableFrom("TempNamespaces", namespaces);
                return o.Query<Page>("GetPageInfoByNamespaces.sql").ToList();
            });
        }

        public static List<Page> GetPageInfoByTags(List<string> tags)
        {
            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var tempTable = o.CreateTempTableFrom("TempTags", tags);
                return o.Query<Page>("GetPageInfoByTags.sql").ToList();
            });
        }

        public static List<Page> GetPageInfoByTag(string tag)
        {
            return ManagedDataStorage.Pages.Ephemeral(o =>
            {
                using var tempTable = o.CreateTempTableFrom("TempTags", new List<string> { tag });
                return o.Query<Page>("GetPageInfoByTags.sql").ToList();
            });
        }

        public static void UpdatePageTags(int pageId, List<string> tags)
        {
            ManagedDataStorage.Pages.Ephemeral(o =>
            {
                tags = tags.Select(o => o.ToLower()).Distinct().ToList();

                using var tempTable = o.CreateTempTableFrom("TempTags", tags);

                var param = new
                {
                    PageId = pageId
                };

                return o.Query<Page>("UpdatePageTags.sql", param).ToList();
            });
        }

        #endregion
    }
}
