fs = require 'fs'
{Message,
 Actions,
 FileUploadStore,
 DraftStore} = require 'nylas-exports'
{Upload} = FileUploadStore

msgId = "local-123"
fpath = "/foo/bar/test123.jpg"
fDir = "/foo/bar"
uploadDir = "/uploads"
filename = "test123.jpg"

xdescribe 'FileUploadStore', ->
  beforeEach ->
    @draft = new Message()
    @session =
      changes:
        add: jasmine.createSpy('session.changes.add')
        commit: ->
      draft: => @draft
    spyOn(NylasEnv, "isMainWindow").andReturn(true)
    spyOn(DraftStore, "sessionForClientId").andReturn(Promise.resolve(@session))
    spyOn(FileUploadStore, "_onAttachFileError").andCallFake (msg) ->
      throw new Error(msg)
    spyOn(NylasEnv, "showOpenDialog").andCallFake (props, callback) ->
      callback(fpath)

  describe 'selectAttachment', ->
    it "throws if no headerMessageId is provided", ->
      expect( -> Actions.selectAttachment()).toThrow()

    it "throws if headerMessageId is blank", ->
      expect( -> Actions.selectAttachment("")).toThrow()

    it "dispatches action to attach file", ->
      spyOn(Actions, "addAttachment")

      Actions.selectAttachment(headerMessageId: msgId)
      expect(NylasEnv.showOpenDialog).toHaveBeenCalled()
      expect(Actions.addAttachment).toHaveBeenCalled()
      args = Actions.addAttachment.calls[0].args[0]
      expect(args.headerMessageId).toBe(msgId)
      expect(args.filePath).toBe(fpath)


  describe 'addAttachment', ->
    beforeEach ->
      @stats =  {
        size: 1234,
        isDirectory: -> false,
      }
      @upload = new Upload({
        headerMessageId: msgId,
        filePath: fpath,
        stats: @stats,
        id: 'u1',
        uploadDir: uploadDir
      })
      spyOn(FileUploadStore, '_getFileStats').andCallFake => Promise.resolve(@stats)
      spyOn(FileUploadStore, '_prepareTargetDir').andCallFake => Promise.resolve()
      spyOn(FileUploadStore, '_copyUpload').andCallFake => Promise.resolve(@upload)
      spyOn(FileUploadStore, '_applySessionChanges').andCallThrough()

    it "throws if no headerMessageId or path is provided", ->
      expect(-> Actions.addAttachment()).toThrow()

    it 'throws if upload is a directory', ->
      @stats = {
        isDirectory: -> true
      }
      waitsForPromise ->
        FileUploadStore._onAddAttachment({headerMessageId: msgId, filePath: fpath})
        .then ->
          throw new Error('Expected test to land in catch.')
        .catch (error) ->
          expect(error.message.indexOf(filename + ' is a directory')).not.toBe(-1)

    it 'throws if the file is more than 25MB', ->
      @stats = {
        size: 25*1000000+1,
        isDirectory: -> false,
      }
      waitsForPromise ->
        FileUploadStore._onAddAttachment({headerMessageId: msgId, filePath: fpath})
        .then ->
          throw new Error('Expected test to land in catch.')
        .catch (error) ->
          expect(error.message.indexOf(filename + ' cannot')).not.toBe(-1)

    it "executes the required steps and triggers", ->
      waitsForPromise ->
        FileUploadStore._onAddAttachment({headerMessageId: msgId, filePath: fpath})

      runs =>
        expect(FileUploadStore._getFileStats).toHaveBeenCalled()
        expect(FileUploadStore._prepareTargetDir).toHaveBeenCalled()
        expect(FileUploadStore._copyUpload).toHaveBeenCalled()
        expect(FileUploadStore._applySessionChanges).toHaveBeenCalled()
        expect(@session.changes.add).toHaveBeenCalledWith({uploads: [@upload]})


  describe 'removeAttachment', ->
    beforeEach ->
      @upload = new Upload({
        headerMessageId: msgId,
        filePath: fpath,
        stats: {
          size: 1234,
          isDirectory: -> false
        },
        id: 'u1',
        uploadDir: uploadDir
      })
      spyOn(FileUploadStore, '_deleteUpload').andCallFake => Promise.resolve(@upload)
      spyOn(fs, 'rmdir')

    it 'removes the upload from the draft', ->
      @draft.uploads = [{id: 'u2'}, @upload]
      waitsForPromise =>
        FileUploadStore._onRemoveAttachment(@upload)
        .then =>
          expect(@session.changes.add).toHaveBeenCalledWith uploads: [{id: 'u2'}]
          expect(fs.rmdir).not.toHaveBeenCalled()

    it 'calls deleteUpload to clean up the filesystem', ->
      @draft.uploads = [@upload]
      waitsForPromise =>
        FileUploadStore._onRemoveAttachment(@upload)
        .then =>
          expect(FileUploadStore._deleteUpload).toHaveBeenCalled()

  describe "when a draft is sent", ->
    it "should delete its uploads directory", ->
      spyOn(FileUploadStore, '_deleteUploadsForId')
      Actions.ensureMessageInSentSuccess({headerMessageId: '123'})
      expect(FileUploadStore._deleteUploadsForId).toHaveBeenCalledWith('123')

  describe '_getFileStats', ->
    it 'returns the correct stats', ->
      spyOn(fs, 'stat').andCallFake (path, callback) ->
        callback(null, {size: 1234, isDirectory: -> false})
      waitsForPromise ->
        FileUploadStore._getFileStats(fpath)
        .then (stats) ->
          expect(stats.size).toEqual 1234
          expect(stats.isDirectory()).toBe false

    it 'throws when there is an error reading the file', ->
      spyOn(fs, 'stat').andCallFake (path, callback) ->
        callback("Error!", null)
      waitsForPromise ->
        FileUploadStore._getFileStats(fpath)
        .then -> throw new Error('It should fail.')
        .catch (error) ->
          expect(error.message.indexOf(fpath)).toBe 0


  describe '_copyUpload', ->
    beforeEach ->
      stream = require 'stream'
      @upload = new Upload({
        headerMessageId: msgId,
        filePath: fpath,
        stats: {
          size: 1234,
          isDirectory: -> false
        },
        id: null,
        uploadDir: uploadDir
      })
      @readStream = stream.Readable()
      @writeStream = stream.Writable()
      spyOn(@readStream, 'pipe')
      spyOn(fs, 'createReadStream').andReturn @readStream
      spyOn(fs, 'createWriteStream').andReturn @writeStream

    it 'copies the file correctly', ->
      waitsForPromise =>
        promise = FileUploadStore._copyUpload(@upload)
        @readStream.emit 'end'
        promise.then (up) =>
          expect(fs.createReadStream).toHaveBeenCalledWith(fpath)
          expect(fs.createWriteStream).toHaveBeenCalledWith(@upload.targetPath)
          expect(@readStream.pipe).toHaveBeenCalledWith(@writeStream)
          expect(up.id).toEqual @upload.id

    it 'throws when there is an error on the read stream', ->
      waitsForPromise =>
        promise = FileUploadStore._copyUpload(@upload)
        @readStream.emit 'error'
        promise
        .then => throw new Error('It should fail.')
        .catch (msg) =>
          expect(msg).not.toBeUndefined()

    it 'throws when there is an error on the write stream', ->
      waitsForPromise =>
        promise = FileUploadStore._copyUpload(@upload)
        @writeStream.emit 'error'
        promise
        .then => throw new Error('It should fail.')
        .catch (msg) =>
          expect(msg).not.toBeUndefined()
