/*jslint indent: 2, maxlen: 80, nomen: true */
/*global window, define, module, test_util, jIO, local_storage, test, ok,
  deepEqual, sinon, expect, stop, start, Blob */

// define([module_name], [dependencies], module);
(function (dependencies, module) {
  "use strict";
  if (typeof define === 'function' && define.amd) {
    return define(dependencies, module);
  }
  module(test_util, jIO, local_storage);
}([
  'test_util',
  'jio',
  'localstorage',
  'qunit'
], function (util, jIO, local_storage) {
  "use strict";

  module("LocalStorage");

  local_storage.clear();

  test("Post & Get", function () {
    expect(6);
    var jio = jIO.createJIO({
      "type": "local",
      "username": "upost",
      "application_name": "apost"
    }, {
      "workspace": {}
    });

    stop();

    jIO.Promise.all([

      jIO.Promise.execute(function () {

        // get inexistent document
        return jio.get({"_id": "inexistent"});

      }).always(function (answer) {

        deepEqual(answer, {
          "error": "not_found",
          "id": "inexistent",
          "message": "Cannot find document",
          "method": "get",
          "reason": "missing",
          "result": "error",
          "status": 404,
          "statusText": "Not Found"
        }, "Get inexistent document");

      }),

      jIO.Promise.execute(function () {

        // post without id
        return jio.post({});

      }).always(function (answer) {
        var uuid = answer.id;
        delete answer.id;
        deepEqual(answer, {
          "method": "post",
          "result": "success",
          "status": 201,
          "statusText": "Created"
        }, "Post without id");

        ok(util.isUuid(uuid), "Uuid should look like " +
           "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx : " + uuid);

      }).then(function () {

        // post non empty document
        return jio.post({"_id": "post1", "title": "myPost1"});

      }).always(function (answer) {

        deepEqual(answer, {
          "id": "post1",
          "method": "post",
          "result": "success",
          "status": 201,
          "statusText": "Created"
        }, "Post");

      }).then(function () {

        return jio.get({"_id": "post1"});

      }).always(function (answer) {

        deepEqual(answer, {
          "data": {
            "_id": "post1",
            "title": "myPost1"
          },
          "id": "post1",
          "method": "get",
          "result": "success",
          "status": 200,
          "statusText": "Ok"
        }, "Get, Check document");

      }).then(function () {

        // post but document already exists
        return jio.post({"_id": "post1", "title": "myPost2"});

      }).always(function (answer) {

        deepEqual(answer, {
          "error": "conflict",
          "id": "post1",
          "message": "Cannot create a new document",
          "method": "post",
          "reason": "document exists",
          "result": "error",
          "status": 409,
          "statusText": "Conflict"
        }, "Post but document already exists");

      })

    ]).always(start);

  });

  test("Put & Get", function () {
    expect(4);
    var jio = jIO.createJIO({
      "type": "local",
      "username": "uput",
      "application_name": "aput"
    }, {
      "workspace": {}
    });

    stop();

    jIO.Promise.execute(function () {

      // put non empty document
      return jio.put({"_id": "put1", "title": "myPut1"});

    }).always(function (answer) {

      deepEqual(answer, {
        "id": "put1",
        "method": "put",
        "result": "success",
        "status": 201,
        "statusText": "Created"
      }, "Creates a document");

    }).then(function () {

      return jio.get({"_id": "put1"});

    }).always(function (answer) {

      deepEqual(answer, {
        "data": {
          "_id": "put1",
          "title": "myPut1"
        },
        "id": "put1",
        "method": "get",
        "result": "success",
        "status": 200,
        "statusText": "Ok"
      }, "Get, Check document");

    }).then(function () {

      // put but document already exists
      return jio.put({"_id": "put1", "title": "myPut2"});

    }).always(function (answer) {

      deepEqual(answer, {
        "id": "put1",
        "method": "put",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Update the document");

    }).then(function () {

      return jio.get({"_id": "put1"});

    }).always(function (answer) {

      deepEqual(answer, {
        "data": {
          "_id": "put1",
          "title": "myPut2"
        },
        "id": "put1",
        "method": "get",
        "result": "success",
        "status": 200,
        "statusText": "Ok"
      }, "Get, Check document");

    }).always(start);

  });

  test("PutAttachment & Get & GetAttachment", function () {
    expect(9);
    var jio = jIO.createJIO({
      "type": "local",
      "username": "uputattmt",
      "application_name": "aputattmt"
    }, {
      "workspace": {}
    });

    stop();

    jIO.Promise.all([

      jIO.Promise.execute(function () {

        // get an attachment from an inexistent document
        return jio.getAttachment({"_id": "inexistent", "_attachment": "a"});

      }).always(function (answer) {

        deepEqual(answer, {
          "attachment": "a",
          "error": "not_found",
          "id": "inexistent",
          "message": "Cannot find document",
          "method": "getAttachment",
          "reason": "missing document",
          "result": "error",
          "status": 404,
          "statusText": "Not Found"
        }, "GetAttachment from inexistent document");

      }),

      jIO.Promise.execute(function () {

        // put a document then get an attachment from the empty document
        return jio.put({"_id": "b"});

      }).then(function () {

        return jio.getAttachment({"_id": "b", "_attachment": "inexistent"});

      }).always(function (answer) {

        deepEqual(answer, {
          "attachment": "inexistent",
          "error": "not_found",
          "id": "b",
          "message": "Cannot find attachment",
          "method": "getAttachment",
          "reason": "missing attachment",
          "result": "error",
          "status": 404,
          "statusText": "Not Found"
        }, "Get inexistent attachment");

      }),

      jIO.Promise.execute(function () {

        // put an attachment to an inexistent document
        return jio.putAttachment({
          "_id": "inexistent",
          "_attachment": "putattmt2",
          "_data": ""
        });

      }).always(function (answer) {

        deepEqual(answer, {
          "attachment": "putattmt2",
          "error": "not_found",
          "id": "inexistent",
          "message": "Impossible to add attachment",
          "method": "putAttachment",
          "reason": "missing",
          "result": "error",
          "status": 404,
          "statusText": "Not Found"
        }, "PutAttachment to inexistent document");

      }),

      jIO.Promise.execute(function () {

        // add a document to the storage
        // don't need to be tested
        return jio.put({"_id": "putattmt1", "title": "myPutAttmt1"});

      }).then(function () {

        return jio.putAttachment({
          "_id": "putattmt1",
          "_attachment": "putattmt2",
          "_data": ""
        });

      }).always(function (answer) {

        deepEqual(answer, {
          "attachment": "putattmt2",
          "digest": "sha256-e3b0c44298fc1c149afbf4c8996fb9242" +
            "7ae41e4649b934ca495991b7852b855", // hex_sha256("")
          "id": "putattmt1",
          "method": "putAttachment",
          "result": "success",
          "status": 200,
          "statusText": "Ok"
        }, "PutAttachment to a document, without data");

      }).then(function () {

        // check document and attachment
        return jIO.Promise.all([
          jio.get({"_id": "putattmt1"}),
          jio.getAttachment({"_id": "putattmt1", "_attachment": "putattmt2"})
        ]);

        // XXX check attachment with a getAttachment

      }).always(function (answers) {

        deepEqual(answers[0], {
          "data": {
            "_attachments": {
              "putattmt2": {
                "content_type": "",
                "digest": "sha256-e3b0c44298fc1c149afbf4c8996fb9242" +
                  "7ae41e4649b934ca495991b7852b855",
                "length": 0
              }
            },
            "_id": "putattmt1",
            "title": "myPutAttmt1"
          },
          "id": "putattmt1",
          "method": "get",
          "result": "success",
          "status": 200,
          "statusText": "Ok"
        }, "Get, Check document");

        ok(answers[1].data instanceof Blob, "Data is Blob");
        deepEqual(answers[1].data.type, "", "Check mimetype");
        deepEqual(answers[1].data.size, 0, "Check size");

        delete answers[1].data;
        deepEqual(answers[1], {
          "attachment": "putattmt2",
          "id": "putattmt1",
          "method": "getAttachment",
          "result": "success",
          "status": 200,
          "statusText": "Ok"
        }, "Get Attachment, Check Response");

      })

    ]).always(start);

  });

  test("Remove & RemoveAttachment", function () {
    expect(4);
    var jio = jIO.createJIO({
      "type": "local",
      "username": "uremove",
      "application_name": "aremove"
    }, {
      "workspace": {}
    });

    stop();

    jIO.Promise.execute(function () {

      return jio.put({"_id": "a"});

    }).then(function () {

      return jio.putAttachment({"_id": "a", "_attachment": "b", "_data": "c"});

    }).then(function () {

      return jio.removeAttachment({"_id": "a", "_attachment": "b"});

    }).always(function (answer) {

      deepEqual(answer, {
        "attachment": "b",
        "id": "a",
        "method": "removeAttachment",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Remove existent attachment");

    }).then(function () {

      // Promise.all always return success
      return jIO.Promise.all([jio.removeAttachment({
        "_id": "a",
        "_attachment": "b"
      })]);

    }).always(function (answers) {

      deepEqual(answers[0], {
        "attachment": "b",
        "error": "not_found",
        "id": "a",
        "message": "Attachment not found",
        "method": "removeAttachment",
        "reason": "missing attachment",
        "result": "error",
        "status": 404,
        "statusText": "Not Found"
      }, "Remove removed attachment");

    }).then(function () {

      return jio.remove({"_id": "a"});

    }).always(function (answer) {

      deepEqual(answer, {
        "id": "a",
        "method": "remove",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Remove existent document");

    }).then(function () {

      return jio.remove({"_id": "a"});

    }).always(function (answer) {

      deepEqual(answer, {
        "error": "not_found",
        "id": "a",
        "message": "Document not found",
        "method": "remove",
        "reason": "missing",
        "result": "error",
        "status": 404,
        "statusText": "Not Found"
      }, "Remove removed document");

    }).always(start);

  });

  test("AllDocs", function () {
    expect(3);
    var o = {}, jio = jIO.createJIO({
      "type": "local",
      "username": "ualldocs",
      "application_name": "aalldocs"
    }, {
      "workspace": {}
    });

    stop();

    jIO.Promise.execute(function () {

      o.date_a = new Date(0);
      o.date_b = new Date();

      // put some document before list them
      return jIO.Promise.all([
        jio.put({
          "_id": "a",
          "title": "one",
          "date": o.date_a
        }).then(function () {
          return jio.putAttachment({
            "_id": "a",
            "_attachment": "aa",
            "_data": "aaa"
          });
        }),
        jio.put({"_id": "b", "title": "two", "date": o.date_a}),
        jio.put({"_id": "c", "title": "one", "date": o.date_b}),
        jio.put({"_id": "d", "title": "two", "date": o.date_b})
      ]);

    }).then(function () {

      // get a list of documents
      return jio.allDocs();

    }).always(function (answer) {

      // sort answer rows for comparison
      if (answer.data && answer.data.rows) {
        answer.data.rows.sort(function (a, b) {
          return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
        });
      }

      deepEqual(answer, {
        "data": {
          "rows": [{
            "id": "a",
            "key": "a",
            "value": {}
          }, {
            "id": "b",
            "key": "b",
            "value": {}
          }, {
            "id": "c",
            "key": "c",
            "value": {}
          }, {
            "id": "d",
            "key": "d",
            "value": {}
          }],
          "total_rows": 4
        },
        "method": "allDocs",
        "result": "success",
        "status": 200,
        "statusText": "Ok"
      }, "AllDocs");

    }).then(function () {

      // get a list of documents
      return jio.allDocs({
        "include_docs": true,
        "sort_on": [['title', 'ascending'], ['date', 'descending']],
        "select_list": ['title', 'date'],
        "limit": [1] // ==> equal [1, 3] in this case
      });

    }).always(function (answer) {

      deepEqual(answer, {
        "data": {
          "rows": [{
            "doc": {
              "_attachments": {
                "aa": {
                  "content_type": "",
                  "digest": "sha256-9834876dcfb05cb167a5c24953eba58c4" +
                    "ac89b1adf57f28f2f9d09af107ee8f0",
                  "length": 3
                }
              },
              "_id": "a",
              "date": o.date_a.toJSON(),
              "title": "one"
            },
            "id": "a",
            "key": "a",
            "value": {
              "date": o.date_a.toJSON(),
              "title": "one"
            }
          }, {
            "doc": {
              "_id": "d",
              "date": o.date_b.toJSON(),
              "title": "two"
            },
            "id": "d",
            "key": "d",
            "value": {
              "date": o.date_b.toJSON(),
              "title": "two"
            }
          }, {
            "doc": {
              "_id": "b",
              "date": o.date_a.toJSON(),
              "title": "two"
            },
            "id": "b",
            "key": "b",
            "value": {
              "date": o.date_a.toJSON(),
              "title": "two"
            }
          }],
          "total_rows": 3
        },
        "method": "allDocs",
        "result": "success",
        "status": 200,
        "statusText": "Ok"
      }, "AllDocs include docs + sort on + limit + select_list");

    }).then(function () {

      // use a query
      return jio.allDocs({
        "query": "title: \"two\"",
        "sort_on": [["date", "descending"]]
      });

    }).always(function (answer) {

      deepEqual(answer, {
        "data": {
          "rows": [{
            "id": "d",
            "key": "d",
            "value": {}
          }, {
            "id": "b",
            "key": "b",
            "value": {}
          }],
          "total_rows": 2
        },
        "method": "allDocs",
        "result": "success",
        "status": 200,
        "statusText": "Ok"
      }, "AllDocs sort on + query");

    }).always(start);

  });

  test("Check & Repair", function () {
    expect(18);
    var o = {}, jio = jIO.createJIO({
      "type": "local",
      "username": "urepair",
      "application_name": "arepair"
    }, {
      "workspace": {}
    });

    stop();

    jIO.Promise.execute(function () {

      o.putCorruptedDocuments = function () {
        // put a document with a wrong attachment reference
        util.json_local_storage.setItem(
          "jio/localstorage/urepair/arepair/war",
          {"_id": "war", "title": "b", "_attachments": {"aa": {}}}
        );

        // put a document with a wrong metadata
        util.json_local_storage.setItem(
          "jio/localstorage/urepair/arepair/meta",
          {"_id": "meta", "title": ["b", ["c", {}], {"blue": "blue"}]}
        );

        // put a corrupted document
        util.json_local_storage.setItem(
          "jio/localstorage/urepair/arepair/cor",
          "blue"
        );
      };

      // put an unreferenced attachment
      util.json_local_storage.setItem(
        "jio/localstorage/urepair/arepair/unref/aa",
        "attachment content"
      );
      o.putCorruptedDocuments();

      return jIO.Promise.all([
        jio.check({"_id": "war"}),
        jio.check({"_id": "meta"}),
        jio.check({"_id": "cor"}),
        jio.check({"_id": "inexistent"})
      ]);

    }).always(function (answers) {

      deepEqual(answers[0], {
        "error": "conflict",
        "id": "war",
        "message": "Attachment \"aa\" of \"war\" is missing",
        "method": "check",
        "reason": "missing attachment",
        "result": "error",
        "status": 409,
        "statusText": "Conflict"
      }, "Check a document with one missing attachment");

      deepEqual(answers[1], {
        "error": "conflict",
        "id": "meta",
        "message": "Some metadata might be lost",
        "method": "check",
        "reason": "corrupted",
        "result": "error",
        "status": 409,
        "statusText": "Conflict"
      }, "Check document with wrong metadata");

      deepEqual(answers[2], {
        "error": "conflict",
        "id": "cor",
        "message": "Document is unrecoverable",
        "method": "check",
        "reason": "corrupted",
        "result": "error",
        "status": 409,
        "statusText": "Conflict"
      }, "Check corrupted document");

      deepEqual(answers[3], {
        "id": "inexistent",
        "method": "check",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Check inexistent document");

    }).then(function () {

      return jIO.Promise.all([
        jio.repair({"_id": "war"}),
        jio.repair({"_id": "meta"}),
        jio.repair({"_id": "cor"}),
        jio.repair({"_id": "inexistent"})
      ]);

    }).always(function (answers) {

      deepEqual(answers[0], {
        "id": "war",
        "method": "repair",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Repair a document with one missing attachment");

      deepEqual(answers[1], {
        "id": "meta",
        "method": "repair",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Repair document with wrong metadata");

      deepEqual(answers[2], {
        "id": "cor",
        "method": "repair",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Repair corrupted document");

      deepEqual(answers[3], {
        "id": "inexistent",
        "method": "repair",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Repair inexistent document");

    }).then(function () {

      o.getCorruptedDocuments = function () {
        return jIO.Promise.all([
          jio.get({"_id": "war"}),
          jio.get({"_id": "meta"}),
          jio.get({"_id": "cor"}),
          jio.get({"_id": "inexistent"})
        ]);
      };

      return o.getCorruptedDocuments();

    }).always(function (answers) {

      o.testGetAnswers = function (answers) {

        deepEqual(answers[0], {
          "data": {
            "_id": "war",
            "title": "b"
          },
          "id": "war",
          "method": "get",
          "result": "success",
          "status": 200,
          "statusText": "Ok"
        }, "Get repaired document with one missing attachment");

        deepEqual(answers[1], {
          "data": {
            "_id": "meta",
            "title": "b"
          },
          "id": "meta",
          "method": "get",
          "result": "success",
          "status": 200,
          "statusText": "Ok"
        }, "Get repaired document with wrong metadata");

        deepEqual(answers[2], {
          "error": "not_found",
          "id": "cor",
          "message": "Cannot find document",
          "method": "get",
          "reason": "missing",
          "result": "error",
          "status": 404,
          "statusText": "Not Found"
        }, "Get repaired corrupted document");

        deepEqual(answers[3], {
          "error": "not_found",
          "id": "inexistent",
          "message": "Cannot find document",
          "method": "get",
          "reason": "missing",
          "result": "error",
          "status": 404,
          "statusText": "Not Found"
        }, "Get repaired inexistent document");

      };

      o.testGetAnswers(answers);

    }).then(function () {

      o.putCorruptedDocuments();

      return jio.repair({});

    }).always(function (answer) {

      deepEqual(answer, {
        "method": "repair",
        "result": "success",
        "status": 204,
        "statusText": "No Content"
      }, "Repair all the database");

    }).then(function () {

      return o.getCorruptedDocuments();

    }).always(function (answers) {

      o.testGetAnswers(answers);

    }).then(function () {

      // unreferenced attachment must be removed
      deepEqual(util.json_local_storage.getItem(
        "jio/localstorage/urepair/arepair/unref/aa"
      ), null, "Unreferenced attachment removed");

    }).always(start);

  });

}));