BASE_DIR = '/var/mold/builds'
path = require 'path'
require 'shelljs/global'
rimraf = require 'rimraf'
util = require './util'

module.exports = class Container
  constructor: (@model, @buildId) ->
    unless @model && @buildId
      throw new Error 'Both model and buildId are required to create a Build'
    @$build = @model.DockerBuild.at @buildId
    unless @dockerBuild = @$build.get()
      throw new Error 'You must create or fetch the DockerBuild before creating a Build'
    @dir = path.resolve BASE_DIR, @buildId

  # git clone / git pull
  pullRepo: (cb) ->
    repo = @dockerBuild.repo
    branch = @dockerBuild.branchName
    @captureLog "Pulling Git Repo: `#{repo}:#{branch}`", {echo: true, slack: true}
    # git clone repo [buildId] [@dir]
    clone = exec("
      git clone git@github.com:#{repo} #{@dir};
      git fetch;
      git checkout #{branch};
    ")
    # TODO: Save logs to consoleOutput
    unless clone.code == 0
      console.error 'Could not clone repo. Git output:\n', clone.output
      return cb new Error "Could not clone repo #{repo} into dir #{@dir}"
    @saveGitSha()
    @captureLog "Cloned `#{repo}:#{branch}`. Latest commit: `#{@$build.get('commitSha')?.slice(0, 12)}`", {echo: true, slack: true}
    return

  saveGitSha: ->
    gitSha = exec "cd #{@dir} && git rev-parse HEAD"
    unless gitSha.code == 0
      console.error 'Error getting git commit sha:\n', gitSha.output
      return cb new Error 'Error getting git commit sha'
    @$build.set 'commitSha', gitSha.output.trim?()

  cleanRepo: (cb) ->
    return cb() unless @dir
    rimraf @dir, cb

  # docker pull
  pullDocker: (cb) ->
    dockerTag = @$build.get 'dockerTag'
    pullCmd = "docker pull #{dockerTag}"
    @captureLog "Running: #{pullCmd}"
    @sendToSlack "Pulling Docker Tag: #{dockerTag}"
    pull = exec pullCmd, (code) ->
      unless code == 0
        console.error 'Could not pull docker image. Docker pull output:\n', pull.output
        throw new Error "Could not pull docker image #{repo}"
        cb()
    @captureLogStream pull.stdout
    return

  # docker build
  build: (cb) ->
    repo = @dockerBuild.repo
    if @dockerBuild.options?.tag
      tag = "#{@dockerBuild.options.tag}"
    else if @dockerBuild.branchName == 'master'
      tag = 'latest'
    else
      tag = "#{@dockerBuild.branchName}"
    return cb new Error 'Oops! Something went wrong and the tag was undefined' unless tag
    dockerTag = "#{repo}:#{tag}"
    buildCmd = "docker build -t #{dockerTag} #{@dir}"
    @captureLog "Running: #{buildCmd}", {echo: true}
    @sendToSlack "Build started: `#{dockerTag}`. Git SHA: `#{@$build.get('commitSha')?.slice(0, 12)}`",
    @$build.set 'stats.buildStartTime', Date.now()
    build = exec buildCmd, (code) =>
      unless code == 0
        console.error 'Cold not build repo. Docker build output:\n', build.output
        return cb new Error "Could not build repo #{@dockerBuild.repo} at dir #{@dir}"
      @$build.set 'stats.buildEndTime', Date.now()
      duration = @$build.get('stats.buildEndTime') - @$build.get('stats.buildStartTime')
      @$build.set 'stats.buildDuration', duration
      @$build.set 'dockerTag', dockerTag
      @saveImageId()
      @sendToSlack "Built `#{dockerTag}`. Docker SHA: `#{@$build.get('imageId')}`"
      cb()
    @captureLogStream build.stdout

  saveImageId: ->
    dockerTag = @$build.get 'dockerTag'
    throw new Error 'Docker tag not found. Cannot save imageId' unless dockerTag
    getImageId = exec(
      # gets the first 12 chars of the docker image's id
      "docker inspect --format='{{.Id}}' #{dockerTag} | cut -c 1-12",
      {silent: true}
    )
    unless getImageId.code == 0
      console.error 'Could not get image id:\n', getImageId.output
      return cb new Error 'There was an error retreiving the id of the built image'
    @$build.set 'imageId', getImageId.output.trim?()

  # docker push
  push: (cb) ->
    dockerTag = @$build.get 'dockerTag'
    return cb new Error 'Docker tag not found. Cannot push build' unless dockerTag
    pushCmd = "docker push #{dockerTag}"
    @captureLog "Running: #{pushCmd}", {echo: true}
    @sendToSlack "Pushing `#{dockerTag}`. Docker SHA: `#{@$build.get('imageId')}`"
    push = exec pushCmd, (code) =>
      unless code == 0
        console.error 'Could not push image', dockerTag
        return cb new Error "Could not push image #{dockerTag}"
      @captureLog "Push Completed `#{dockerTag}`. Docker SHA: `#{@$build.get('imageId')}`", {echo: true, slack: true}
      cb()
    @captureLogStream push.stdout

  saveNpmLs: ->
    imageId = @$build.get 'imageId'
    npmLsCmd = "docker run #{imageId} npm ls"
    @captureLog "Running: #{npmLsCmd}", {echo: true}
    npmLs = exec npmLsCmd, {silent: true}
    unless npmLs.code == 0
      console.error 'Could not generate npm ls', npmLs.output
      return cb new Error "Could not generate npm ls for image: #{imageId}"
    @captureLog npmLs.output, {fromStream: true}
    @$build.set 'npmLsOutput', npmLs.output.trim?()
    return

  # docker push
  deploy: (cb) ->
    # TODO: Track time deployed
    process.nextTick cb
    console.log 'deploying docker'
    # run `restart-[service] [branch]`q

  captureLogStream: (stream) ->
    stream.on 'data', (data) => @captureLog data, {fromStream: true}

  captureLog: (data, options={}) ->
    return unless data
    currentLength = @$build.get('consoleOutput')?.length || 0
    # Add a new line to each log to mimic the behaviour of console.log
    # when manually adding to consoleOoutput
    data += '\n' unless options.fromStream
    @$build.stringInsert 'consoleOutput', currentLength, data
    console.log data.trim?() if options.echo
    @sendToSlack data.trim?() if options.slack

  sendToSlack: (string, cb = -> ) ->
    return unless string
    util.sendToSlack string, cb

