/**
 * This file is part of Eclipse Steady.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.backend.cve;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.eclipse.steady.backend.util.ConnectionUtil;
import org.eclipse.steady.shared.cache.Cache;
import org.eclipse.steady.shared.cache.CacheException;
import org.eclipse.steady.shared.cache.ObjectFetcher;
import org.eclipse.steady.shared.connectivity.Service;
import org.eclipse.steady.shared.connectivity.ServiceConnectionException;
import org.eclipse.steady.shared.util.VulasConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.minidev.json.JSONArray;

/**
 * Reads {@link Cve} information from the {@link Service#CVE}.
 */
public class CveReader2 implements ObjectFetcher<String, Cve> {

  private static Logger log = LoggerFactory.getLogger(CveReader2.class);

  /**
   * Cache entries are invalidated after one day (1440 min) in order to not miss any changes.
   */
  private static Cache<String, Cve> CVE_CACHE = new Cache<String, Cve>(new CveReader2(), 1440);

  /**
   * <p>read.</p>
   *
   * @param _key a {@link java.lang.String} object.
   * @return a {@link org.eclipse.steady.backend.cve.Cve} object.
   * @throws org.eclipse.steady.shared.cache.CacheException if any.
   */
  public static Cve read(String _key) throws CacheException {
    return CVE_CACHE.get(_key);
  }

  /**
   * <p>read.</p>
   *
   * @param _key a {@link java.lang.String} object.
   * @param _force_fetch a boolean.
   * @return a {@link org.eclipse.steady.backend.cve.Cve} object.
   * @throws org.eclipse.steady.shared.cache.CacheException if any.
   */
  public static Cve read(String _key, boolean _force_fetch) throws CacheException {
    return CVE_CACHE.get(_key, _force_fetch);
  }

  /**
   * Returns CVE information for the given key (or null in case the key is null).
   * This information is retrieved from a (remote) service {@link Service#CVE}.
   *
   * @param _key a {@link java.lang.String} object.
   * @return a {@link org.eclipse.steady.backend.cve.Cve} object.
   * @throws org.eclipse.steady.shared.cache.CacheException if any.
   */
  public Cve fetch(String _key) throws CacheException {
    if (_key == null) return null;

    Cve cve = null;
    int sc = -1;
    String result = null;
    String uri = null;

    try {
      final String url = VulasConfiguration.getGlobal().getServiceUrl(Service.CVE, true);

      final CloseableHttpClient httpclient = HttpClients.createDefault();
      uri = url.replaceAll("<ID>", _key);
      log.info("Query details of [" + _key + "] at [" + uri + "]");
      final HttpGet method = new HttpGet(uri);
      if (ConnectionUtil.getProxyConfig() != null)
        method.setConfig(ConnectionUtil.getProxyConfig());
      final CloseableHttpResponse response = httpclient.execute(method);
      try {
        sc = response.getStatusLine().getStatusCode();
        final HttpEntity entity = response.getEntity();
        if (sc == org.apache.http.HttpStatus.SC_OK && entity != null) {
          result = ConnectionUtil.readInputStream(entity.getContent());
          cve = CveReader2.buildFromJson(_key, result);
        } else {
          log.error(
              "HTTP GET [url="
                  + uri
                  + "] completed with ["
                  + sc
                  + "], and entity ["
                  + entity
                  + "]");
        }
      } catch (ParseException e) {
        log.error("HTTP GET [url=" + uri + "] caused an exception: " + e.getMessage());
        throw new CacheException(_key, e);
      } finally {
        response.close();
      }
      log.info("Fetched " + cve + " for key [" + _key + "]");
    } catch (ClientProtocolException e) {
      log.error("HTTP GET [url=" + uri + "] caused an exception: " + e.getMessage());
      throw new CacheException(_key, e);
    } catch (IOException e) {
      log.error("HTTP GET [url=" + uri + "] caused an exception: " + e.getMessage());
      log.error("Error: " + e.getMessage(), e);
      throw new CacheException(_key, e);
    } catch (ServiceConnectionException e) {
      log.error(e.getMessage());
      throw new CacheException(_key, e);
    }
    return cve;
  }

  private static final Cve buildFromJson(String _id, String _json) throws ParseException {
    final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
    final Cve cve = new Cve();
    cve.setId(_id);

    final Configuration conf = Configuration.defaultConfiguration();
    // conf.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL);
    final Object document = conf.jsonProvider().parse(_json);

    // Take first english description
    final JSONArray descriptions =
        JsonPath.read(
            document,
            "$.result.CVE_Items[0].cve.description.description_data[?(@.lang=='en')].value");
    if (descriptions == null || descriptions.size() == 0) {
      log.warn("No english description found for CVE [" + _id + "]");
      cve.setSummary("Not available");
    } else {
      cve.setSummary(descriptions.get(0).toString());
    }

    final String published = JsonPath.read(document, "$.result.CVE_Items[0].publishedDate");
    final Calendar publ = new GregorianCalendar();
    publ.setTime(format.parse(published));
    cve.setPublished(publ);

    final String modified = JsonPath.read(document, "$.result.CVE_Items[0].lastModifiedDate");
    final Calendar modi = new GregorianCalendar();
    modi.setTime(format.parse(modified));
    cve.setModified(modi);

    String cvss3_version = null;
    Double cvss3_score = null;
    String cvss3_vector = null;
    try {
      cvss3_version =
          JsonPath.read(document, "$.result.CVE_Items[0].impact.baseMetricV3.cvssV3.version");
      cvss3_score =
          JsonPath.read(document, "$.result.CVE_Items[0].impact.baseMetricV3.cvssV3.baseScore");
      cvss3_vector =
          JsonPath.read(document, "$.result.CVE_Items[0].impact.baseMetricV3.cvssV3.vectorString");
    } catch (Exception e) {
      log.warn(
          "Exception when reading CVSS v3 information for CVE [" + _id + "]: " + e.getMessage());
    }

    String cvss2_version = null;
    Double cvss2_score = null;
    String cvss2_vector = null;
    try {
      cvss2_version =
          JsonPath.read(document, "$.result.CVE_Items[0].impact.baseMetricV2.cvssV2.version");
      cvss2_score =
          JsonPath.read(document, "$.result.CVE_Items[0].impact.baseMetricV2.cvssV2.baseScore");
      cvss2_vector =
          JsonPath.read(document, "$.result.CVE_Items[0].impact.baseMetricV2.cvssV2.vectorString");
    } catch (Exception e) {
      log.warn(
          "Exception when reading CVSS v2 information for CVE [" + _id + "]: " + e.getMessage());
    }

    if (cvss3_version != null && cvss3_score != null && cvss3_vector != null) {
      cve.setCvssScore(cvss3_score.floatValue());
      cve.setCvssVector(cvss3_vector);
      cve.setCvssVersion(cvss3_version);
    } else if (cvss2_version != null && cvss2_score != null && cvss2_vector != null) {
      cve.setCvssScore(cvss2_score.floatValue());
      cve.setCvssVector(cvss2_vector);
      cve.setCvssVersion(cvss2_version);
    }

    return cve;
  }
}
