/**
 * NewsStorageService
 *
 * Coordinates category validation, refresh logic, and repository access.
 */
class NewsStorageService {
  constructor(categoryCatalog, repository, apiClient, pinnedArticleService = null) {
    this._categoryCatalog = categoryCatalog
    this._repository = repository
    this._apiClient = apiClient
    this._pinnedArticleService = pinnedArticleService
  }

  /**
   * @returns {Promise<object[]>}
   */
  async refreshAll() {
    const results = []
    for (const category of this._categoryCatalog.all()) {
      results.push(await this.refreshCategory(category.key))
    }
    return results
  }

  /**
   * @param {string} categoryKey
   * @returns {Promise<object>}
   */
  async refreshCategory(categoryKey) {
    const category = this._resolveCategory(categoryKey)
    const articles = await this._apiClient.fetchArticlesByCategory(category)
    return this._repository.saveCategory(category, articles)
  }

  /**
   * @param {string} categoryKey
   * @param {number} limit
   * @returns {Promise<object>}
   */
  async getCategoryNews(categoryKey, limit) {
    const category = this._resolveCategory(categoryKey)
    const document = await this._repository.readCategory(category)
    const pinnedDocument = await this._applyPinned(category, document)
    return this._applyLimit(pinnedDocument, limit)
  }

  /**
   * @param {string} categoryKey
   * @param {number} page
   * @param {number} pageSize
   * @returns {Promise<object>}
   */
  async getCategoryNewsPage(categoryKey, page, pageSize) {
    const category = this._resolveCategory(categoryKey)
    const document = await this._repository.readCategory(category)
    const pinnedDocument = await this._applyPinned(category, document)
    const articles = pinnedDocument.articles
    const safePage = Number.isInteger(page) && page > 0 ? page : 1
    const safePageSize = Number.isInteger(pageSize) && pageSize > 0 ? pageSize : articles.length
    const start = (safePage - 1) * safePageSize
    const end = start + safePageSize

    return {
      ...pinnedDocument,
      total: pinnedDocument.total,
      page: safePage,
      pageSize: safePageSize,
      articles: articles.slice(start, end)
    }
  }

  /**
   * @param {number} limit
   * @returns {Promise<object[]>}
   */
  async getAllNews(limit) {
    const results = []
    for (const category of this._categoryCatalog.all()) {
      const document = await this._repository.readCategory(category)
      results.push(this._applyLimit(document, limit))
    }
    return results
  }

  /**
   * @returns {object[]}
   */
  getCategories() {
    return this._categoryCatalog.all().map((category) => ({
      key: category.key,
      label: category.label,
      query: category.query,
      fileName: category.fileName,
      apiUrl: category.apiUrl
    }))
  }

  /**
   * @param {string} categoryKey
   * @returns {import('../config/CategoryCatalog').NewsCategory}
   */
  _resolveCategory(categoryKey) {
    const category = this._categoryCatalog.getByKey(categoryKey)
    if (!category) {
      throw new Error(`Unsupported category: ${categoryKey}`)
    }
    return category
  }

  /**
   * Prepends the configured pinned articles to a category document, removing any
   * duplicate of a pinned article already present in the fetched list so it only
   * appears once (at the top). Returns the document unchanged when no pinning
   * service is configured or no pins apply to the category.
   *
   * @param {import('../config/CategoryCatalog').NewsCategory} category
   * @param {object} document
   * @returns {Promise<object>}
   */
  async _applyPinned(category, document) {
    if (!this._pinnedArticleService) {
      return document
    }
    const pinned = await this._pinnedArticleService.getPinnedArticles(category.key)
    if (!pinned.length) {
      return document
    }

    const pinnedUrls = new Set(pinned.map((article) => article.url))
    const rest = document.articles.filter((article) => !pinnedUrls.has(article.url))
    const articles = [...pinned, ...rest]

    return {
      ...document,
      total: articles.length,
      articles
    }
  }

  /**
   * @param {object} document
   * @param {number} limit
   * @returns {object}
   */
  _applyLimit(document, limit) {
    if (!limit || limit < 1) {
      return document
    }
    return {
      ...document,
      total: Math.min(document.total, limit),
      articles: document.articles.slice(0, limit)
    }
  }
}

module.exports = {
  NewsStorageService
}
