/*
 * Copyright 2017 Google Inc.
 *
 * 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.
 */

package com.google.firebase.testing;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.client.googleapis.util.Utils;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.json.GenericJson;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

public class IntegrationTestUtils {

  private static final String IT_SERVICE_ACCOUNT_PATH = "integration_cert.json";
  private static final String IT_API_KEY_PATH = "integration_apikey.txt";

  private static GenericJson serviceAccount;
  private static String apiKey;
  private static FirebaseApp masterApp;

  private static synchronized GenericJson ensureServiceAccount() {
    if (serviceAccount == null) {
      try (InputStream stream = new FileInputStream(IT_SERVICE_ACCOUNT_PATH)) {
        serviceAccount = Utils.getDefaultJsonFactory().fromInputStream(stream, GenericJson.class);
      } catch (IOException e) {
        String msg = String.format("Failed to read service account certificate from %s. "
            + "Integration tests require a service account credential obtained from a Firebase "
            + "project. See CONTRIBUTING.md for more details.", IT_SERVICE_ACCOUNT_PATH);
        throw new RuntimeException(msg, e);
      }
    }
    return serviceAccount;
  }

  public static InputStream getServiceAccountCertificate() {
    return new ByteArrayInputStream(ensureServiceAccount().toString().getBytes());
  }

  public static String getProjectId() {
    return ensureServiceAccount().get("project_id").toString();
  }

  public static String getDatabaseUrl() {
    return "https://" + getProjectId() + ".firebaseio.com";
  }

  public static String getStorageBucket() {
    return getProjectId() + ".appspot.com";
  }

  public static synchronized String getApiKey() {
    if (apiKey == null) {
      try (InputStream stream = new FileInputStream(IT_API_KEY_PATH)) {
        apiKey = CharStreams.toString(new InputStreamReader(stream)).trim();
      } catch (IOException e) {
        String msg = String.format("Failed to read API key from %s. "
            + "Integration tests require an API key obtained from a Firebase "
            + "project. See CONTRIBUTING.md for more details.", IT_API_KEY_PATH);
        throw new RuntimeException(msg, e);
      }
    }
    return apiKey;
  }

  /**
   * Initializes the default FirebaseApp for integration testing (if not already initialized), and
   * returns it. Integration tests that interact with the default FirebaseApp should call this
   * method to obtain the app instance. This method ensures that all integration tests get the
   * same FirebaseApp instance, instead of initializing an app per test.
   *
   * @return the default FirebaseApp instance
   */
  public static synchronized FirebaseApp ensureDefaultApp() {
    if (masterApp == null) {
      FirebaseOptions options =
          FirebaseOptions.builder()
              .setDatabaseUrl(getDatabaseUrl())
              .setStorageBucket(getStorageBucket())
              .setCredentials(TestUtils.getCertCredential(getServiceAccountCertificate()))
              .setFirestoreOptions(FirestoreOptions.newBuilder()
                  .setTimestampsInSnapshotsEnabled(true)
                  .setCredentials(TestUtils.getCertCredential(getServiceAccountCertificate()))
                  .build())
              .build();
      masterApp = FirebaseApp.initializeApp(options);
    }
    return masterApp;
  }

  public static FirebaseApp initApp(String name) {
    FirebaseOptions options =
        new FirebaseOptions.Builder()
            .setDatabaseUrl(getDatabaseUrl())
            .setCredentials(TestUtils.getCertCredential(getServiceAccountCertificate()))
            .build();
    return FirebaseApp.initializeApp(options, name);
  }

  public static DatabaseReference getRandomNode(FirebaseApp app) {
    return getRandomNode(app, 1).get(0);
  }

  public static List<DatabaseReference> getRandomNode(FirebaseApp app, int count) {
    FirebaseDatabase database = FirebaseDatabase.getInstance(app);
    ImmutableList.Builder<DatabaseReference> builder = ImmutableList.builder();
    String name = null;
    for (int i = 0; i < count; i++) {
      if (name == null) {
        DatabaseReference ref = database.getReference().push();
        builder.add(ref);
        name = ref.getKey();
      } else {
        builder.add(database.getReference().child(name));
      }
    }
    return builder.build();
  }
  
  public static class AppHttpClient {

    private final FirebaseApp app;
    private final FirebaseOptions options;
    private final HttpRequestFactory requestFactory;
    
    public AppHttpClient() {
      this(FirebaseApp.getInstance());
    }
    
    public AppHttpClient(FirebaseApp app) {
      this.app = checkNotNull(app);
      this.options = app.getOptions();
      this.requestFactory = this.options.getHttpTransport().createRequestFactory();
    }
    
    public ResponseInfo put(String path, String json) throws IOException {
      String url = options.getDatabaseUrl() + path + "?access_token=" + getToken();
      HttpRequest request = requestFactory.buildPutRequest(new GenericUrl(url),
          ByteArrayContent.fromString("application/json", json));
      HttpResponse response = null;
      try {
        response = request.execute();
        return new ResponseInfo(response);
      } finally {
        if (response != null) {
          response.disconnect();
        }
      }
    }
    
    private String getToken() {
      // TODO: We should consider exposing getToken (or similar) publicly for the
      // purpose of servers doing authenticated REST requests like this.
      return TestOnlyImplFirebaseTrampolines.getToken(app, false);
    }
  }
  
  public static class ResponseInfo {
    private final int status;
    private final byte[] payload;
    
    private ResponseInfo(HttpResponse response) throws IOException {
      this.status = response.getStatusCode();
      this.payload = ByteStreams.toByteArray(response.getContent());
    }

    public int getStatus() {
      return status;
    }

    public byte[] getPayload() {
      return payload;
    }
  }
}