/*!
 * Copyright (c) 2021, 2023 Eclipse Foundation, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * Contributors:
 *   Eric Poirier <eric.poirier@eclipse-foundation.org>
 *   Olivier Goulet <olivier.goulet@eclipse-foundation.org>
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import 'isomorphic-fetch';
import { getNextPage, transformSnakeToCamel, validateDate } from './utils';
import { APIResponse } from '../types/api';
import { Ad, NewsroomResource } from '../types/models';

const apiBasePath = 'https://newsroom.eclipse.org/api';

const newsroomResourceMapper = (resource: any): NewsroomResource => {
  let mappedResource = transformSnakeToCamel(resource) as any;
  
  // Convert resource ID to a number.
  mappedResource.id = Number.parseInt(mappedResource.id);
  if (isNaN(mappedResource.id)) {
    throw TypeError('Newsroom resource ID is not a number');
  }

  // Validate resource date.
  const timestamp = new Date(mappedResource.date);
  if (!validateDate(timestamp)) {
    throw TypeError('Newsroom resource date is invalid');
  }

  // Remove `landingLink` property if the resource doesn't have one.
  // The API can have it as either string or false. So a falsy value means the
  // resource doesn't have a landing link.
  if (!mappedResource.landingLink) {
    delete mappedResource.landingLink;
  }

  // Omit unused fields from author object.
  mappedResource.author = {
    fullName: mappedResource.author.fullName
  }

  return mappedResource;   
}

interface GetNewsroomResourcesOptions {
  /** The number of newsroom resources to be retrieved on a single request. */
  pageSize?: number;
  /** 
    * The resource types to be retrieved. Ex. 'case_study', 'social_media_kit',
    * 'white_paper'. 
    */
  resourceTypes?: string[];
  /** The publishing destinations to be retrieved. Ex. 'eclipse_org'. */
  publishTo?: string[];
}

/**
  * Retrieve newsroom resources.
  */
export const getNewsroomResources = async ({ 
  pageSize, 
  resourceTypes, 
  publishTo 
}: GetNewsroomResourcesOptions = {}): Promise<APIResponse<NewsroomResource[]>> => {
  try {
    const url = new URL(`${apiBasePath}/resources`);
    resourceTypes && resourceTypes.forEach(t => url.searchParams.append('parameters[resource_type][]', t));
    publishTo && publishTo.forEach(p => url.searchParams.append('parameters[publish_to][]', p));
    pageSize && url.searchParams.append('pagesize', pageSize.toString());

    let newsroomResources: NewsroomResource[] = [];

    let nextPage = url;
    while (nextPage) {
      const response = await fetch(nextPage.href);
      if (!response.ok) throw new Error('Could not fetch newsroom resources');

      const data = await response.json();
      if (data && data.resources) { 
        newsroomResources = newsroomResources.concat(data.resources.map(newsroomResourceMapper));
      }

      // If `pageSize` is set and the current amount of newsroom resources surpasses it, break the loop.
      // We may still have extra newsroom resources going over the limit but at least we won't fetch any more.
      // Extra newsroom resources will be removed later.
      if (pageSize && newsroomResources.length >= pageSize) break;

      const linkHeader = response.headers.get('Link');
      nextPage = getNextPage(linkHeader);
    }

    // If pageSize is set, remove the extra newsroom resources.
    if (pageSize) {
      newsroomResources.splice(pageSize);
    }

    return [newsroomResources, null];
  } catch (error) {
    console.error(error);

    return [null, error];
  }
}

const adsMapper = (ad: any): Ad => {
  let mappedAd = transformSnakeToCamel(ad) as any;

  // Convert ad ID to a number.
  mappedAd.id = Number.parseInt(mappedAd.id);
  if (isNaN(mappedAd.id)) {
    throw TypeError('Ad ID is not a number');
  }

  mappedAd.campaignName = Number.parseInt(mappedAd.campaignName);
  if (isNaN(mappedAd.campaignName)) {
    throw TypeError('Ad campaign name is not a number');
  }

  return {
    id: mappedAd.id,
    campaignName: mappedAd.campaignName,
    format: mappedAd.format,
    image: mappedAd.image,
    publishTo: mappedAd.publishTo,
    url: mappedAd.url,
  };
}

interface GetAdsOptions {
  /** The website to retrieve ads for. Ex. 'eclipse_org'. */
  publishTo: string;
  /** The formats of the ads to be retrieved. Ex. 'ads_square'. */
  formats: string[];
}

/**
  * Retrieve ads for a specified website.
  *
  * @param options - The options to be used when retrieving ads.
  */
export const getAds = async (options: GetAdsOptions): Promise<APIResponse<Ad[]>> => {
  try {
    if (!options.publishTo) throw new TypeError('Missing required option: publishTo');
    if (!options.formats) throw new TypeError('Missing required option: formats');

    const url = `${apiBasePath}/ads`;
    const payload = {
      host: window.location.host,
      source: window.location.pathname,
      publish_to: options.publishTo,
      format: options.formats,
    }

    // Make the request to the API.
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });

    if (!response.ok) throw new Error('Could not fetch ads');

    const data = await response.json();
    const ads = data.map(adsMapper);

    return [ads, null];
  } catch (error) {
    return [null, error];
  }
};
