makeTests = (_, assert, expect, btoa, Octokit) ->

  USERNAME = 'octokit-test'
  TOKEN = 'dca7f85a5911df8e9b7aeb4c5be8f5f50806ac49'

  REPO_USER = USERNAME
  REPO_NAME = 'octokit-test-repo' # Cannot use '.' because najax does not like it

  OTHER_USERNAME = 'octokit-test2'

  LONG_TIMEOUT = 10 * 1000 # 10 seconds
  SHORT_TIMEOUT = 5 * 1000 # 5 seconds


  IS_NODE = !! module?


  trapFail = (promise) ->
    promise.fail (err) ->
      console.error(JSON.stringify(err))
      assert.fail(err)
    return promise


  helper1 = (done, promise, func) ->
    return trapFail(promise)
    .then(func)
    .then () -> done()

  helper2 = (promise, func) ->
    return trapFail(promise)
    .then(func)

  arrayContainsKey = (arr, key, value) ->
    _.some arr, (entry) ->
      return entry[key] == value


  describe 'Octokit', () ->
    @timeout(LONG_TIMEOUT)

    GH = 'GH'
    REPO = 'REPO'
    USER = 'USER'
    BRANCH = 'BRANCH'
    ANOTHER_USER = 'ANOTHER_USER'

    STATE = {}

    itIsOk = (obj, funcName) ->
      it "##{funcName}()", (done) ->
        helper1 done, STATE[obj][funcName](), (val) ->
          expect(val).to.be.ok

    before () ->
      options =
        token: TOKEN
        # PhantomJS does not support the `PATCH` verb yet.
        # See https://github.com/ariya/phantomjs/issues/11384 for updates
        usePostInsteadOfPatch:true

      options.useETags = false if IS_NODE

      STATE[GH] = new Octokit(options)
      STATE[USER] = STATE[GH].getUser()

    itIsOk(GH, 'getZen')
    itIsOk(GH, 'getAllUsers')
    #(GH, 'getOrgRepos(orgName, type='all')')
    itIsOk(GH, 'getPublicGists')
    itIsOk(GH, 'getPublicEvents')
    #(GH, 'onRateLimitChanged(listener)')

    describe 'Repo:', () ->
      @timeout(LONG_TIMEOUT)
      PREV_SHA = null

      before (done) ->
        STATE[REPO] = STATE[GH].getRepo(REPO_USER, REPO_NAME)
        STATE[BRANCH] = STATE[REPO].getDefaultBranch()
        createRepo = () ->
          console.log('BEFORE: Creating test repo')
          options =
            description: 'Test Repository for https:/github.com/philschatz/octokit.js'
            homepage: 'https:/github.com/philschatz/octokit.js'
            # private: false
            # has_issues: false
            # has_wiki: false
            # has_downloads: false
            auto_init: true # Make an initial `master` branch and commit

          trapFail(STATE[USER].createRepo(REPO_NAME, options))
          .done () ->
            console.log('BEFORE: Done Creating test repo')
            setTimeout(done, SHORT_TIMEOUT)

        resetRepoToFirstCommit = (repoInfo) ->
          findLastCommits = (commits, options) ->
            console.log('BEFORE: Found commits')
            if options?.next
              trapFail(options.next())
              .done(findLastCommits)
            else
              console.log('BEFORE: Finding master branch')
              initialCommit = commits[commits.length-1]
              sha = initialCommit.sha

              masterBranch = repoInfo.master_branch
              console.log('BEFORE: Found master branch')
              console.log("BEFORE: Updating #{masterBranch} to #{sha}")
              trapFail(STATE[REPO].git.updateHead(masterBranch, sha, true)) # true == force
              .done () =>
                console.log('BEFORE: Updated HEAD')
                done()

          console.log('BEFORE: Resetting test repo')
          trapFail(STATE[REPO].getCommits())
          .done (findLastCommits)

        # Make sure the repo is empty by deleting it and creating a new one
        console.log('BEFORE: Checking if repo exists')
        STATE[REPO].getInfo()
        .fail(() -> createRepo())
        .done (val) ->
          # HACK: najax seems to ignore the HTTP Status
          if 'Not Found' == val.message
            return createRepo()
          # STATE[REPO].git.deleteRepo()
          # .fail(() -> resetRepoToFirstCommit)
          # .done () -> createRepo()

          # Send the repoInfo so we do not rely on cached results (najax does not like that for now)
          resetRepoToFirstCommit(val)

      describe 'Initially:', () ->
        it 'has one commit', (done) ->
          trapFail(STATE[REPO].getCommits())
          .done (val) ->
            expect(val).to.have.length(1)
            done()

        it 'has one branch', (done) ->
          trapFail(STATE[REPO].getBranches())
          .done (branches) ->
            expect(branches).to.have.length(1)
            done()

      describe 'Writing file(s):', () ->
        it 'commits a single text file', (done) ->
          FILE_PATH = 'test.txt'
          FILE_TEXT = 'Hello there'

          trapFail(STATE[BRANCH].write(FILE_PATH, FILE_TEXT))
          .done (sha) ->
            # Read the file back
            trapFail(STATE[BRANCH].read(FILE_PATH))
            .done (val) ->
              expect(val.content).to.equal(FILE_TEXT)
              PREV_SHA = val.sha
              done()

        it 'commits multiple files at once (including binary ones)', (done) ->
          FILE1 = 'testdir/test1.txt'
          FILE2 = 'testdir/test2.txt'
          BINARY_DATA = 'Ahoy!'
          contents = {}
          contents[FILE1] = 'Hello World!'
          contents[FILE2] = {content:btoa(BINARY_DATA), isBase64:true}

          trapFail(STATE[BRANCH].writeMany(contents))
          .done () ->
            # Read the files and verify they were added
            trapFail(STATE[BRANCH].read(FILE1))
            .done (val) ->
              expect(val.content).to.equal(contents[FILE1])
              trapFail(STATE[BRANCH].read(FILE2))
              .done (val) ->
                expect(val.content).to.equal(contents[FILE2].content)
                done()

        it 'should have created 3 commits (2 + the initial)', (done) ->
          helper1 done, STATE[REPO].getCommits(), (commits) ->
            expect(commits).to.have.length(3)

      describe 'Collaborators:', () ->
        it 'initially should have only 1 collaborator', (done) ->
          helper1 done, STATE[REPO].getCollaborators(), (collaborators) ->
            expect(collaborators).to.have.length(1)

        if IS_NODE
          console.log('SKIPPING: najax does not support boolean GET requests yet (204/404 responses) so skipping a few for now')
        else

          it 'initially the collaborator should be [USERNAME]', (done) ->
            helper1 done, STATE[REPO].isCollaborator(USERNAME), (canCollaborate) ->
              expect(canCollaborate).to.be.true

          it 'the current user should be able to collaborate', (done) ->
            helper1 done, STATE[REPO].canCollaborate(), (canCollaborate) ->
              expect(canCollaborate).to.be.true

          it 'should be able to add and remove a collaborator', (done) ->
            helper2 STATE[REPO].addCollaborator(OTHER_USERNAME), (added) ->
              expect(added).to.be.true

              helper2 STATE[REPO].isCollaborator(OTHER_USERNAME), (canCollaborate) ->
                expect(canCollaborate).to.be.true

                helper2 STATE[REPO].removeCollaborator(OTHER_USERNAME), (removed) ->
                  expect(removed).to.be.true

                  helper1 done, STATE[REPO].isCollaborator(OTHER_USERNAME), (canCollaborate) ->
                    expect(canCollaborate).to.be.false

      describe 'Events:', () ->
        itIsOk(REPO, 'getEvents')
        itIsOk(REPO, 'getIssueEvents')
        itIsOk(REPO, 'getNetworkEvents')
        #itIsOk(REPO, 'getNotifications')

      describe 'Misc:', () ->
        itIsOk(REPO, 'getHooks')
        itIsOk(REPO, 'getLanguages')
        itIsOk(REPO, 'getInfo')


    describe 'Users:', () ->

      describe 'Current User:', () ->

        describe 'Methods for all Users:', () ->
          #itIsOk(USER, 'getNotifications')
          itIsOk(USER, 'getInfo')
          itIsOk(USER, 'getRepos')
          itIsOk(USER, 'getOrgs')
          itIsOk(USER, 'getGists')
          itIsOk(USER, 'getFollowers')
          itIsOk(USER, 'getFollowing')
          #(USER, 'isFollowing')
          itIsOk(USER, 'getPublicKeys')
          # itIsOk(USER, 'getReceivedEvents')
          # itIsOk(USER, 'getEvents')

        describe 'Methods only for authenticated user:', () ->
          #(USER, 'updateInfo(options')
          itIsOk(USER, 'getGists')
          #(USER, 'follow(username)')
          #(USER, 'unfollow(username)')
          itIsOk(USER, 'getEmails')
          #(USER, 'addEmail(emails)')
          #(USER, 'removeEmail(emails)')
          #(USER, 'getPublicKey(id)')
          #(USER, 'addPublicKey(title, key)')
          #(USER, 'updatePublicKey(id, options)')

      describe 'Another User:', () ->
        before () ->
          STATE[ANOTHER_USER] = STATE[GH].getUser(OTHER_USERNAME)

        # itIsOk(user, 'getNotifications')
        itIsOk(ANOTHER_USER, 'getInfo')
        itIsOk(ANOTHER_USER, 'getRepos')
        itIsOk(ANOTHER_USER, 'getOrgs')
        itIsOk(ANOTHER_USER, 'getGists')
        itIsOk(ANOTHER_USER, 'getFollowers')
        itIsOk(ANOTHER_USER, 'getFollowing')
        #(ANOTHER_USER, 'isFollowing')
        itIsOk(ANOTHER_USER, 'getPublicKeys')
        itIsOk(ANOTHER_USER, 'getReceivedEvents')
        itIsOk(ANOTHER_USER, 'getEvents')


if exports?
  exports.makeTests = makeTests
else
  @makeTests = makeTests
