#include "bcesdk/bos/client.h"
#include "bcesdk/model/bce_response.h"
#include <fcntl.h>
#include <iostream>
#include <stdio.h>
#include <thread>
#include "api_test.h"
#include "api_demo_config.h"
#include "bcesdk/bos/sts_client.h"

using namespace baidu::bos::cppsdk;

std::string g_bucket = Config::BUCKET;
std::string g_object = Config::OBJECT;

void print_common_response(BceResponse &result) {
    printf("status:%d\n", result.status_code());
    if (result.is_ok()) {
        printf("request-id:%s\n", result.request_id().c_str());
        printf("debug-id:%s\n", result.debug_id().c_str());
    }
    if (result.is_fail()) {
        printf("error-message:%s\n", result.error().message().c_str());
        printf("error-code: %s\n", result.error().code().c_str());
        printf("curl-error-code: %s\n", result.error().detailed_curl_error().c_str());
    }
}

void list_buckets(Client &client) {
    ListBucketsRequest request;
    ListBucketsResponse response;
    client.list_buckets(request, &response);
}

void test_bucket_head(Client &client) {
    HeadBucketRequest request(g_bucket);
    HeadBucketResponse response;
    client.head_bucket(request, &response);
    BucketType type = response.bucket_type();
    printf("g_bucket type:%d", type);
    print_common_response(response);
}

void test_bucket_acl(Client &client) {
    GetBucketAclRequest request(g_bucket);
    GetBucketAclResponse response;
    client.get_bucket_acl(request, &response);
    print_common_response(response);
}

void test_bucket_versioning(Client &client) {
    GetBucketVersioningRequest request(g_bucket);
    GetBucketVersioningResponse response;
    client.get_bucket_versioning(request, &response);
    print_common_response(response);
    std::cout << "versioning:" << response.versioning_status() << std::endl;
}

void test_put_bucket_versioning(Client &client) {
    PutBucketVersioningRequest request(g_bucket, "enabled");
    PutBucketVersioningResponse response;
    client.put_bucket_versioning(request, &response);
    print_common_response(response);
}

void test_bucket_location(Client &client) {
    GetBucketLocationRequest request(g_bucket);
    GetBucketLocationResponse response;
    client.get_bucket_location(request, &response);
    print_common_response(response);
    printf("location:%s\n", response.location().c_str());
}

void test_rename(Client& client) {
    RenameObjectRequest req(g_bucket, "bcecmd_rename", "bcecmd");
    RenameObjectResponse res;
    client.rename_object(req, &res);
    print_common_response(res);
}

void test_list_objects(Client &client) {
    ListObjectsRequest request(g_bucket);
    request.set_prefix("szy-bj-public-read_logs/");
    ListObjectsResponse result;
    client.list_objects(request, &result);
    print_common_response(result);
    if (result.is_fail()) {
        return;
    }
    std::vector<ObjectSummary> &summary = result.contents();
    for (size_t i = 0; i < summary.size(); ++i) {
        printf("%s %s\n", summary[i].key.c_str(), summary[i].storage_class.c_str());
    }
    for (size_t i = 0; i < result.common_prefixes().size(); ++i) {
        printf("dir %s\n", result.common_prefixes().at(i).c_str());
    }
}

void test_list_object_versions(Client &client){
    ListObjectVersionsRequest request(g_bucket);
    ListObjectVersionsResponse result;
    client.list_object_versions(request, &result);
    print_common_response(result);
    if (result.is_fail()) {
        return;
    }
    std::vector<ObjectVersionSummary> &summary = result.contents();
    for (size_t i = 0; i < summary.size(); ++i) {
        printf("%s %s %s %s deleted:%s\n",
            summary[i].key.c_str(),
            summary[i].storage_class.c_str(),
            summary[i].version_id.c_str(),
            summary[i].is_latest ? "true" : "false",
            summary[i].delete_marker ? "true" : "false");
    }
}

void test_head_object(Client &client) {
    HeadObjectRequest request(g_bucket, g_object);
    request.set_version_id("");
    HeadObjectResponse result;
    int ret = client.head_object(request, &result);
    printf("ret:%d\n", ret);
    print_common_response(result);
    printf("content-type: %s\n", result.meta().content_type().c_str());
    printf("content-disposition: %s\n", result.meta().content_disposition().c_str());
    printf("content-encoding: %s\n", result.meta().content_encoding().c_str());
    printf("last-modified: %ld\n", (long)result.meta().last_modified());
    printf("expires: %ld\n", (long)result.meta().expires());
    printf("storage class: %s\n", result.meta().storage_class().c_str());
    printf("etag: %s\n", result.meta().etag().c_str());
    printf("md5: %s\n", result.meta().content_md5().c_str());
    printf("x-bce-meta-uid: %s\n", result.meta().user_meta("uid").c_str());
    printf("x-bce-next-append-offset: %llu\n", result.meta().next_append_offset());
    RestoreStatus s = result.meta().restore_status();
    printf("ongoing : %d\n", s.ongoing);
    printf("epiry-date: %s\n", s.expiry_date.c_str());
    printf("version_id: %s\n", result.meta().version_id().c_str());
}

void test_put_object(Client &client) {
    std::string filename = g_object;
    FileInputStream file(filename);
    PutObjectRequest request(g_bucket, g_object, &file);
    PutObjectResponse result;
    client.put_object(request, &result);
    print_common_response(result);
    printf("etag: %s\n", result.etag().c_str());
    printf("version id: %s\n", result.version_id().c_str());
}

void test_endpoint_switch(Client &client) {
    std::string filename = g_object;
    FileInputStream file(filename);
    PutObjectRequest request(g_bucket, g_object, &file);
    PutObjectResponse result;
    client.put_object(request, &result);
    print_common_response(result);
    printf("etag: %s\n", result.etag().c_str());
}

void test_append_object(Client &client) {
    std::string filename = g_object;

    uint64_t off = 0;
    {
        // append first
        FileInputStream file(filename);
        AppendObjectRequest request(g_bucket, g_object, &file);
        AppendObjectResponse result;

        client.append_object(request, &result);
        print_common_response(result);
        printf("etag: %s\n", result.etag().c_str());
        printf("x-bce-next-append-offset: %llu\n", result.next_append_offset());
        off = result.next_append_offset();
    }
    {
        // append again
        FileInputStream file(filename);
        AppendObjectRequest request(g_bucket, g_object, &file);
        AppendObjectResponse result;
        request.set_offset(off);

        client.append_object(request, &result);
        print_common_response(result);
        printf("etag: %s\n", result.etag().c_str());
        printf("x-bce-next-append-offset: %llu\n", result.next_append_offset());
    }
}


void test_abort(Client &client) {
    ListMultipartUploadsRequest request(g_bucket);
    ListMultipartUploadsResponse result;
    client.list_multipart_uploads(request, &result);
    print_common_response(result);
    if (result.is_fail()) {
        return;
    }
    std::vector<MultipartUploadSummary> &uploads = result.uploads();
    for (size_t i = 0; i < uploads.size(); ++i) {
        printf("key: %s\n", uploads[i].key.c_str());
        AbortMultipartUploadRequest abort_req(g_bucket, uploads[i].key, uploads[i].upload_id);
        AbortMultipartUploadResponse abort_res;
        client.abort_multipart_upload(abort_req, &abort_res);
        printf("abort status::%d\n", abort_res.status_code());
    }
}

void test_copy(Client &client) {
    std::string dst_bucket = g_bucket;
    std::string dst_object = g_object;
    // CopyObjectRequest request(dst_bucket, dst_object, g_bucket, g_object);
    CopyObjectRequest request(dst_bucket, dst_object, "szy-bj-test", "sample-cloud.jpeg");
    CopyObjectResponse response;
    int ret = client.copy_object(request, &response);
    std::cout << ret << std::endl;
    print_common_response(response);
    // if (response.is_fail()) {
    //     return;
    // }
    printf("statusCode: %d\n", response.status_code());
    printf("etag: %s\n", response.etag().c_str());
    printf("last_modified: %ld\n", (long)response.last_modified());
    printf("error_code: %s\n", response.error().code().c_str());
}

void test_copy_version(Client &client) {
    std::string dst_bucket = g_bucket;
    std::string dst_object = g_object + ".bak";
    std::string version_id = "AN+Qh0G4bc4=";
    CopyObjectRequest request(dst_bucket, dst_object, g_bucket, g_object, version_id);
    // CopyObjectRequest request(dst_bucket, dst_object, "szy-bj-test", "sample-cloud.jpeg");
    CopyObjectResponse response;
    int ret = client.copy_object(request, &response);
    print_common_response(response);
    if (response.is_fail()) {
        return;
    }
    printf("statusCode: %d\n", response.status_code());
    printf("etag: %s\n", response.etag().c_str());
    printf("last_modified: %ld\n", (long)response.last_modified());
    printf("version_id: %s\n", response.dest_version_id().c_str());
}

void test_delete_object_version(Client &client) {
    std::string version_id = "AFWQTkUTmYI=";
    DeleteObjectRequest request(g_bucket, g_object, version_id);
    DeleteObjectResponse response;
    client.delete_object(request, &response);
    print_common_response(response);
    printf("delete version_id: %s\n", response.deleted_version_id().c_str());
}

void test_delete_object(Client &client) {
    DeleteObjectRequest request(g_bucket, g_object);
    DeleteObjectResponse response;
    client.delete_object(request, &response);
    print_common_response(response);
    printf("delete version_id: %s\n", response.deleted_version_id().c_str());
}

void test_download(Client &client) {
    std::string filename = g_object;
    FileOutputStream file("non-exist/" + filename);
    GetObjectRequest request(g_bucket, g_object);
    GetObjectResponse response(&file);
    client.get_object(request, &response);
    print_common_response(response);
}

void test_download_version(Client &client) {
    std::string filename = g_object;
    FileOutputStream file(filename);
    GetObjectRequest request(g_bucket, g_object);
    request.set_version_id("AL6QXEXJmJ0=");
    GetObjectResponse response(&file);
    client.get_object(request, &response);
    print_common_response(response);
    std::cout << response.meta().version_id() << std::endl;
}

void test_multipart_upload(Client &client){
    InitMultiUploadRequest initMultiUploadRequest(g_bucket, g_object);
    InitMultiUploadResponse initMultiUploadResponse;
    int ret = client.init_multipart_upload(initMultiUploadRequest, &initMultiUploadResponse);
    std::string upload_id = initMultiUploadResponse.upload_id();
    printf("upload_id: %s", upload_id.c_str());

    // upload parts
    long partSize = 1024 * 1024 * 5L;
    std::vector <part_t> partEtags;
    std::string partFileName = "../" + g_object;
    FileInputStream file(partFileName);
    int partCount = static_cast<int>(file.get_size() / partSize);
    if (file.get_size() % partSize != 0){
        partCount++;
    }
    int64_t size = file.get_size();
    int64_t off = 0;
    for (int i = 0; off < file.get_size(); ++i) {
        if (off + partSize > size) {
            partSize = size - off;
        }
        FileInputStream partFile(file.fd(), off, partSize);
        UploadPartRequest uploadPartRequest(g_bucket, g_object , &partFile, i + 1, upload_id);
        UploadPartResponse uploadPartResponse;

        ret = client.upload_part(uploadPartRequest, &uploadPartResponse);
        if (ret != 0){
            printf("Fail to upload part!!!");
            return;
        }
        // Save the part etag to a list
        part_t partInfo;
        partInfo.part_number = i+1;
        partInfo.etag = uploadPartResponse.etag();
        partEtags.push_back(partInfo);

        off += partSize;
    }

    CompleteMultipartUploadRequest completeMultipartUploadRequest(g_bucket, g_object, upload_id);
    for (part_t partInfo : partEtags) {
        completeMultipartUploadRequest.add_part(partInfo.part_number, partInfo.etag);
    }
    CompleteMultipartUploadResponse completeMultipartUploadResponse;
    ret = client.complete_multipart_upload(completeMultipartUploadRequest, &completeMultipartUploadResponse);

    // print the etag
    std::cout << completeMultipartUploadResponse.etag() << std::endl;
}

void test_restore(Client &client) {
    std::string filename = g_object;
    RestoreObjectRequest request(g_bucket, g_object);
    RestoreObjectResponse response;
    client.restore_object(request, &response);
    print_common_response(response);
}

void test_multiple_download(Client &client) {
    std::string filename = g_object;
    FileOutputStream file(filename);
    int ret = client.parallel_download(g_bucket, g_object, file);
    printf("ret:%d\n", ret);
}

void test_multiple_upload(Client &client) {
    std::string filename = g_object;
    FileInputStream file(filename);
    ObjectMetaData meta;
    meta.set_storage_class("STANDARD_IA");
    int ret = client.parallel_upload(g_bucket, g_object, file, &meta);
    printf("ret:%d\n", ret);
}

void test_parallel_copy(Client &client) {
    std::string dst_bucket = g_bucket;
    std::string dst_object = g_object + ".bak";
    int ret = client.parallel_copy(g_bucket, g_object, dst_bucket, dst_object);
    printf("ret:%d\n", ret);
}

void test_sym_link(Client& client) {
    std::string g_bucket = "g_bucket-symlink";
    std::string g_object = "abc";
    PutSymlinkRequest put_request(g_bucket, g_object);
    put_request.set_symlink_target("target");
    BceResponse put_response;
    client.put_symlink(put_request, &put_response);
    print_common_response(put_response);

    GetSymlinkRequest get_request(g_bucket, g_object);
    GetSymlinkResponse get_response;
    client.get_symlink(get_request, &get_response);
    print_common_response(get_response);
    std::cout << get_response.symlink() << std::endl;

    DeleteObjectRequest del_request(g_bucket, g_object);
    DeleteObjectResponse del_response;
    client.delete_object(g_bucket, g_object);
    print_common_response(del_response);
    client.get_symlink(get_request, &get_response);
    std::cout << get_response.symlink() << std::endl;
}

/**
 * @param stsClient
 */
void test_assume_role(StsClient &stsClient) {
    AssumeRoleRequest assumeRoleRequest("3600", "", "BceServiceRole_BOS_Inventory");
    AssumeRoleResponse assumeRoleResponse;
    int ret = stsClient.AssumeRole(assumeRoleRequest, &assumeRoleResponse);
    printf("assume role ret:%d\n", ret);
}

void threadPutObject(Client* client, PutObjectRequest* request, PutObjectResponse* response){
    //request.set_request_cancelable(true);
    client->put_object(*request, response);
    std::cout << "1\n";
    print_common_response(*response);
}

void threadChangeCancel(Client* client, PutObjectRequest* request){
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    client->cancel_request(*request);
}

void test_cancel(Client& client){
    FileInputStream file(g_object);
    FileInputStream file2("README.md");
    PutObjectRequest request(g_bucket, g_object, &file);
    PutObjectResponse response;
    PutObjectRequest request2(g_bucket, g_object, &file2);
    PutObjectResponse response2;
    request.set_request_cancelable(true);
    request2.set_request_cancelable(true);
    std::thread t1(threadPutObject, &client, &request, &response);
    std::thread t3(threadPutObject, &client, &request2, &response2);
    std::thread t2(threadChangeCancel, &client, &request);
    std::thread t4(threadChangeCancel, &client, &request2);

    t1.join();
    t2.join();
    t3.join();
    t4.join();
}

int main() {
    FILE *logfp = fopen("sdk.log", "w");
    sdk_set_log_stream(logfp);
    sdk_set_log_level(SDK_LOG_DEBUG);

    ClientOptions option;
    option.max_parallel = 10;
    option.connect_timeout_ms = 1000;
    option.timeout = 10;
    option.retry = 3;
    option.endpoint = "bj.bcebos.com";

    std::string ak = Config::AK;
    std::string sk = Config::SK;
    Client client(ak, sk, option);

    // test_multipart_upload(client);
    // test_endpoint_switch(client);
    // test_put_bucket_versioning(client);
    // test_bucket_versioning(client);
    // test_list_object_versions(client);
    // test_download_version(client);
    // test_copy_version(client);
    // test_put_object(client);
    //test_restore(client);
    //test_rename(client);

    //StsClient stsClient("", "");
    //test_assume_role(stsClient);
    // test_put_object(client);
    // test_copy(client);
    // test_delete_object(client);
    // test_list_objects(client);
    //test_append_object(client);
    // test_head_object(client);
    //test_abort(client);
    //test_multiple_download(client);
    // test_download(client);
    //test_multiple_upload(client);
    //test_bucket_acl(client);
    //list_buckets(client);
    //test_parallel_copy(client);
    //test_bucket_location(client);

    //test more bos api
    //srand((unsigned int)time(NULL));
    //std::string temp_dir = "/tmp";
    //std::string  test_bucket = "your-test-g_bucket";
    //if (false == test_object_api(client, temp_dir, test_bucket)) {
    //    printf("test_object_api fail!");
    //    return -1;
    //}
    //if (false == test_bucket_api(client)) {
    //    printf("test_bucket_api fail!");
    //    return -1;
    //}
    //if (false == test_multipart_api(client, temp_dir, test_bucket)) {
    //    printf("test_multipart_api fail!");
    //    return -1;
    //}
    //if (false == test_large_file(client, temp_dir, test_bucket) ){
    //    printf("test_large_file fail!");
    //    return -1;
    //}
    return 0;
}
