/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.jclouds.b2.features;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static org.jclouds.b2.features.B2TestUtils.api;
import static org.jclouds.b2.features.B2TestUtils.assertAuthentication;
import static org.jclouds.b2.features.B2TestUtils.assertRequest;
import static org.jclouds.b2.features.B2TestUtils.createMockWebServer;
import static org.jclouds.b2.features.B2TestUtils.stringFromResource;

import java.net.URI;
import java.util.Date;
import java.util.Map;

import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;

import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.KeyNotFoundException;
import org.jclouds.http.options.GetOptions;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.b2.domain.Action;
import org.jclouds.b2.domain.B2Object;
import org.jclouds.b2.domain.B2ObjectList;
import org.jclouds.b2.domain.DeleteFileResponse;
import org.jclouds.b2.domain.HideFileResponse;
import org.jclouds.b2.domain.UploadFileResponse;
import org.jclouds.b2.domain.UploadUrlResponse;
import org.jclouds.b2.reference.B2Headers;
import org.testng.annotations.Test;

import com.google.common.collect.ImmutableMap;
import com.google.common.net.HttpHeaders;


@Test(groups = "unit", testName = "ObjectApiMockTest")
public final class ObjectApiMockTest {
   private static final String BUCKET_NAME = "BUCKET_NAME";
   private static final String BUCKET_ID = "4a48fe8875c6214145260818";
   private static final String FILE_ID = "4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104";
   private static final String FILE_NAME = "typing_test.txt";
   private static final String CONTENT_TYPE = "text/plain";
   private static final String SHA1 = "bae5ed658ab3546aee12f23f36392f35dba1ebdd";
   private static final String PAYLOAD = "The quick brown fox jumped over the lazy dog.\n";
   private static final Map<String, String> FILE_INFO = ImmutableMap.of("author", "unknown");

   public void testGetUploadUrl() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setBody(stringFromResource("/get_upload_url_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         UploadUrlResponse response = api.getUploadUrl(BUCKET_ID);
         assertThat(response.bucketId()).isEqualTo(BUCKET_ID);
         assertThat(response.uploadUrl()).isEqualTo(URI.create("https://pod-000-1005-03.backblaze.com/b2api/v2/b2_upload_file?cvt=c001_v0001005_t0027&bucket=4a48fe8875c6214145260818"));
         assertThat(response.authorizationToken()).isEqualTo("2_20151009170037_f504a0f39a0f4e657337e624_9754dde94359bd7b8f1445c8f4cc1a231a33f714_upld");

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_get_upload_url", "/get_upload_url_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testGetUploadUrlDeletedBucket() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setResponseCode(400).setBody(stringFromResource("/get_upload_url_deleted_bucket_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         try {
            api.getUploadUrl(BUCKET_ID);
            failBecauseExceptionWasNotThrown(ContainerNotFoundException.class);
         } catch (ContainerNotFoundException cnfe) {
            // expected
         }

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_get_upload_url", "/get_upload_url_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testUploadFile() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/upload_file_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         String accountId = "d522aa47a10f";

         UploadUrlResponse uploadUrl = UploadUrlResponse.create(BUCKET_ID, server.url("/b2api/v2/b2_upload_file/4a48fe8875c6214145260818/c001_v0001007_t0042").uri(), "FAKE-AUTHORIZATION-TOKEN");
         Payload payload = Payloads.newStringPayload(PAYLOAD);
         payload.getContentMetadata().setContentType(CONTENT_TYPE);
         UploadFileResponse response = api.uploadFile(uploadUrl, FILE_NAME, SHA1, FILE_INFO, payload);

         assertThat(response.fileId()).isEqualTo(FILE_ID);
         assertThat(response.fileName()).isEqualTo(FILE_NAME);
         assertThat(response.accountId()).isEqualTo(accountId);
         assertThat(response.bucketId()).isEqualTo(BUCKET_ID);
         assertThat(response.contentLength()).isEqualTo(PAYLOAD.length());
         assertThat(response.contentSha1()).isEqualTo(SHA1);
         assertThat(response.contentType()).isEqualTo(CONTENT_TYPE);
         assertThat(response.fileInfo()).isEqualTo(FILE_INFO);

         assertThat(server.getRequestCount()).isEqualTo(1);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_upload_file/4a48fe8875c6214145260818/c001_v0001007_t0042");
      } finally {
         server.shutdown();
      }
   }

   public void testDeleteFileVersion() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setBody(stringFromResource("/delete_object_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         DeleteFileResponse response = api.deleteFileVersion(FILE_NAME, FILE_ID);
         assertThat(response.fileName()).isEqualTo(FILE_NAME);
         assertThat(response.fileId()).isEqualTo(FILE_ID);

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_delete_file_version", "/delete_object_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testDeleteAlreadyDeletedFileVersion() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setResponseCode(400).setBody(stringFromResource("/delete_file_version_already_deleted_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         try {
            api.deleteFileVersion(FILE_NAME, FILE_ID);
            failBecauseExceptionWasNotThrown(KeyNotFoundException.class);
         } catch (KeyNotFoundException knfe) {
            // expected
         }

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_delete_file_version", "/delete_object_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testGetFileInfo() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setBody(stringFromResource("/get_file_info_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         B2Object b2Object = api.getFileInfo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001");
         assertThat(b2Object.fileId()).isEqualTo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001");
         assertThat(b2Object.fileName()).isEqualTo("akitty.jpg");
         assertThat(b2Object.accountId()).isEqualTo("7eecc42b9675");
         assertThat(b2Object.bucketId()).isEqualTo("e73ede9c9c8412db49f60715");
         assertThat(b2Object.contentLength()).isEqualTo(122573);
         assertThat(b2Object.contentSha1()).isEqualTo("a01a21253a07fb08a354acd30f3a6f32abb76821");
         assertThat(b2Object.contentType()).isEqualTo("image/jpeg");
         assertThat(b2Object.fileInfo()).isEqualTo(ImmutableMap.<String, String>of());
         assertThat(b2Object.action()).isEqualTo(Action.UPLOAD);
         assertThat(b2Object.uploadTimestamp()).isAfterYear(2014);
         assertThat(b2Object.payload()).isNull();

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_get_file_info", "/get_file_info_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testGetFileInfoDeletedFileVersion() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setResponseCode(404).setBody(stringFromResource("/get_file_info_deleted_file_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         B2Object b2Object = api.getFileInfo("4_ze73ede9c9c8412db49f60715_f100b4e93fbae6252_d20150824_m224353_c900_v8881000_t0001");
         assertThat(b2Object).isNull();

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_get_file_info", "/get_file_info_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testDownloadFileById() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));

      server.enqueue(new MockResponse()
            .addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE)
            .addHeader(B2Headers.FILE_ID, FILE_ID)
            .addHeader(B2Headers.FILE_NAME, FILE_NAME)
            .addHeader(B2Headers.CONTENT_SHA1, SHA1)
            .addHeader(B2Headers.UPLOAD_TIMESTAMP, String.valueOf(1500000000000L))
            .addHeader(B2Headers.FILE_INFO_PREFIX + FILE_INFO.entrySet().iterator().next().getKey(), FILE_INFO.entrySet().iterator().next().getValue())
            .setBody(PAYLOAD));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();

         B2Object b2Object = api.downloadFileById(FILE_ID);

         assertThat(b2Object.fileId()).isEqualTo(FILE_ID);
         assertThat(b2Object.fileName()).isEqualTo(FILE_NAME);
         assertThat(b2Object.contentSha1()).isEqualTo(SHA1);
         assertThat(b2Object.fileInfo()).isEqualTo(FILE_INFO);
         assertThat(b2Object.uploadTimestamp()).isAfterYear(2015);
         assertThat(b2Object.payload().getContentMetadata().getContentLength()).isEqualTo(PAYLOAD.length());
         assertThat(b2Object.payload().getContentMetadata().getContentType()).isEqualTo(CONTENT_TYPE);

         assertThat(server.getRequestCount()).isEqualTo(2);

         RecordedRequest request = server.takeRequest();
         assertThat(request.getMethod()).isEqualTo("GET");
         assertThat(request.getPath()).isEqualTo("/b2api/v2/b2_authorize_account");

         request = server.takeRequest();
         assertThat(request.getMethod()).isEqualTo("GET");
         assertThat(request.getPath()).isEqualTo("/b2api/v2/b2_download_file_by_id?fileId=4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104");
      } finally {
         server.shutdown();
      }
   }

   public void testDownloadFileByIdOptions() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));

      server.enqueue(new MockResponse()
            .addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE)
            .addHeader(B2Headers.FILE_ID, FILE_ID)
            .addHeader(B2Headers.FILE_NAME, FILE_NAME)
            .addHeader(B2Headers.CONTENT_SHA1, SHA1)
            .addHeader(B2Headers.UPLOAD_TIMESTAMP, String.valueOf(1500000000000L))
            .addHeader(B2Headers.FILE_INFO_PREFIX + FILE_INFO.entrySet().iterator().next().getKey(), FILE_INFO.entrySet().iterator().next().getValue())
            .setBody(PAYLOAD));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();

         B2Object b2Object = api.downloadFileById(FILE_ID, new GetOptions().range(42, 69));

         assertThat(server.getRequestCount()).isEqualTo(2);

         RecordedRequest request = server.takeRequest();
         assertThat(request.getMethod()).isEqualTo("GET");
         assertThat(request.getPath()).isEqualTo("/b2api/v2/b2_authorize_account");

         request = server.takeRequest();
         assertThat(request.getMethod()).isEqualTo("GET");
         assertThat(request.getPath()).isEqualTo("/b2api/v2/b2_download_file_by_id?fileId=4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104");
         assertThat(request.getHeaders().values("Range")).contains("bytes=42-69");
      } finally {
         server.shutdown();
      }
   }

   public void testDownloadFileByName() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));

      server.enqueue(new MockResponse()
            .addHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE)
            .addHeader(B2Headers.FILE_ID, FILE_ID)
            .addHeader(B2Headers.FILE_NAME, FILE_NAME)
            .addHeader(B2Headers.CONTENT_SHA1, SHA1)
            .addHeader(B2Headers.UPLOAD_TIMESTAMP, String.valueOf(1500000000000L))
            .addHeader(B2Headers.FILE_INFO_PREFIX + FILE_INFO.entrySet().iterator().next().getKey(), FILE_INFO.entrySet().iterator().next().getValue())
            .setBody(PAYLOAD));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();

         B2Object b2Object = api.downloadFileByName(BUCKET_NAME, FILE_NAME);

         assertThat(b2Object.fileId()).isEqualTo(FILE_ID);
         assertThat(b2Object.fileName()).isEqualTo(FILE_NAME);
         assertThat(b2Object.contentSha1()).isEqualTo(SHA1);
         assertThat(b2Object.fileInfo()).isEqualTo(FILE_INFO);
         assertThat(b2Object.uploadTimestamp()).isAfterYear(2015);
         assertThat(b2Object.payload().getContentMetadata().getContentLength()).isEqualTo(PAYLOAD.length());
         assertThat(b2Object.payload().getContentMetadata().getContentType()).isEqualTo(CONTENT_TYPE);

         assertThat(server.getRequestCount()).isEqualTo(2);

         RecordedRequest request = server.takeRequest();
         assertThat(request.getMethod()).isEqualTo("GET");
         assertThat(request.getPath()).isEqualTo("/b2api/v2/b2_authorize_account");

         request = server.takeRequest();
         assertThat(request.getMethod()).isEqualTo("GET");
         assertThat(request.getPath()).isEqualTo("/file/BUCKET_NAME/typing_test.txt");
      } finally {
         server.shutdown();
      }
   }

   public void testListFileNames() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setBody(stringFromResource("/list_file_names_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         String accountId = "d522aa47a10f";

         B2ObjectList list = api.listFileNames(BUCKET_ID, null, null, null, null);

         assertThat(list.nextFileName()).isNull();
         assertThat(list.files()).hasSize(2);

         B2ObjectList.Entry object = list.files().get(0);
         assertThat(object.action()).isEqualTo(Action.UPLOAD);
         assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f1004ba650fe24e6b_d20150809_m012853_c100_v0009990_t0000");
         assertThat(object.fileName()).isEqualTo("files/hello.txt");
         assertThat(object.size()).isEqualTo(6);
         assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439083733000L));

         object = list.files().get(1);
         assertThat(object.action()).isEqualTo(Action.UPLOAD);
         assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f1004ba650fe24e6c_d20150809_m012854_c100_v0009990_t0000");
         assertThat(object.fileName()).isEqualTo("files/world.txt");
         assertThat(object.size()).isEqualTo(6);
         assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439083734000L));

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_list_file_names", "/list_file_names_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testListFileVersions() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setBody(stringFromResource("/list_file_versions_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         String accountId = "d522aa47a10f";

         B2ObjectList list = api.listFileVersions(BUCKET_ID, null, null, null, null, null);

         assertThat(list.nextFileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886247_d20150809_m232316_c100_v0009990_t0003");
         assertThat(list.nextFileName()).isEqualTo("files/world.txt");
         assertThat(list.files()).hasSize(3);

         B2ObjectList.Entry object = list.files().get(0);
         assertThat(object.action()).isEqualTo(Action.UPLOAD);
         assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886245_d20150809_m232316_c100_v0009990_t0003");
         assertThat(object.fileName()).isEqualTo("files/hello.txt");
         assertThat(object.size()).isEqualTo(6);
         assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439162596000L));

         object = list.files().get(1);
         assertThat(object.action()).isEqualTo(Action.HIDE);
         assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886247_d20150809_m232323_c100_v0009990_t0005");
         assertThat(object.fileName()).isEqualTo("files/world.txt");
         assertThat(object.size()).isEqualTo(0);
         assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439162603000L));

         object = list.files().get(2);
         assertThat(object.action()).isEqualTo(Action.UPLOAD);
         assertThat(object.fileId()).isEqualTo("4_z27c88f1d182b150646ff0b16_f100920ddab886246_d20150809_m232316_c100_v0009990_t0003");
         assertThat(object.fileName()).isEqualTo("files/world.txt");
         assertThat(object.size()).isEqualTo(6);
         assertThat(object.uploadTimestamp()).isEqualTo(new Date(1439162596000L));

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_list_file_versions", "/list_file_versions_request.json");
      } finally {
         server.shutdown();
      }
   }

   public void testHideFile() throws Exception {
      MockWebServer server = createMockWebServer();
      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
      server.enqueue(new MockResponse().setBody(stringFromResource("/hide_file_response.json")));

      try {
         ObjectApi api = api(server.url("/").toString(), "b2").getObjectApi();
         String accountId = "d522aa47a10f";

         HideFileResponse response = api.hideFile(BUCKET_ID, FILE_NAME);
         assertThat(response.action()).isEqualTo(Action.HIDE);
         assertThat(response.fileId()).isEqualTo("4_h4a48fe8875c6214145260818_f000000000000472a_d20140104_m032022_c001_v0000123_t0104");
         assertThat(response.fileName()).isEqualTo(FILE_NAME);
         assertThat(response.uploadTimestamp()).isEqualTo(new Date(1437815673000L));

         assertThat(server.getRequestCount()).isEqualTo(2);
         assertAuthentication(server);
         assertRequest(server.takeRequest(), "POST", "/b2api/v2/b2_hide_file", "/hide_file_request.json");
      } finally {
         server.shutdown();
      }
   }
}
