var fs = require('fs')
var path = require('path')
var request = require('teeny-request').teenyRequest
var urlgrey = require('urlgrey')
var jsYaml = require('js-yaml')
var walk = require('ignore-walk')
var execSync = require('child_process').execSync

var detectProvider = require('./detect')

var version = 'v' + require('../package.json').version

var patterns,
  more_patterns = ''

var isWindows =
  process.platform.match(/win32/) || process.platform.match(/win64/)

if (!isWindows) {
  patterns =
    "-type f \\( -name '*coverage.*' " +
    "-or -name 'nosetests.xml' " +
    "-or -name 'jacoco*.xml' " +
    "-or -name 'clover.xml' " +
    "-or -name 'report.xml' " +
    "-or -name 'cobertura.xml' " +
    "-or -name 'luacov.report.out' " +
    "-or -name 'lcov.info' " +
    "-or -name '*.lcov' " +
    "-or -name 'gcov.info' " +
    "-or -name '*.gcov' " +
    "-or -name '*.lst' \\) " +
    "-not -name '*.sh' " +
    "-not -name '*.data' " +
    "-not -name '*.py' " +
    "-not -name '*.class' " +
    "-not -name '*.xcconfig' " +
    "-not -name 'Coverage.profdata' " +
    "-not -name 'phpunit-code-coverage.xml' " +
    "-not -name 'coverage.serialized' " +
    "-not -name '*.pyc' " +
    "-not -name '*.cfg' " +
    "-not -name '*.egg' " +
    "-not -name '*.whl' " +
    "-not -name '*.html' " +
    "-not -name '*.js' " +
    "-not -name '*.cpp' " +
    "-not -name 'coverage.jade' " +
    "-not -name 'include.lst' " +
    "-not -name 'inputFiles.lst' " +
    "-not -name 'createdFiles.lst' " +
    "-not -name 'coverage.html' " +
    "-not -name 'scoverage.measurements.*' " +
    "-not -name 'test_*_coverage.txt' " +
    "-not -path '*/vendor/*' " +
    "-not -path '*/htmlcov/*' " +
    "-not -path '*/home/cainus/*' " +
    "-not -path '*/virtualenv/*' " +
    "-not -path '*/js/generated/coverage/*' " +
    "-not -path '*/.virtualenv/*' " +
    "-not -path '*/virtualenvs/*' " +
    "-not -path '*/.virtualenvs/*' " +
    "-not -path '*/.env/*' " +
    "-not -path '*/.envs/*' " +
    "-not -path '*/env/*' " +
    "-not -path '*/envs/*' " +
    "-not -path '*/.venv/*' " +
    "-not -path '*/.venvs/*' " +
    "-not -path '*/venv/*' " +
    "-not -path '*/venvs/*' " +
    "-not -path '*/.git/*' " +
    "-not -path '*/.hg/*' " +
    "-not -path '*/.tox/*' " +
    "-not -path '*/__pycache__/*' " +
    "-not -path '*/.egg-info*' " +
    "-not -path '*/$bower_components/*' " +
    "-not -path '*/node_modules/*' " +
    "-not -path '*/conftest_*.c.gcov'"
} else {
  patterns =
    '/a-d /b /s *coverage.* ' +
    '/s nosetests.xml ' +
    '/s jacoco*.xml ' +
    '/s clover.xml ' +
    '/s report.xml ' +
    '/s cobertura.xml ' +
    '/s luacov.report.out ' +
    '/s lcov.info ' +
    '/s *.lcov ' +
    '/s gcov.info ' +
    '/s *.gcov ' +
    '/s *.lst' +
    '| findstr /i /v \\.sh$ ' +
    '| findstr /i /v \\.data$ ' +
    '| findstr /i /v \\.py$ ' +
    '| findstr /i /v \\.class$ ' +
    '| findstr /i /v \\.xcconfig$ ' +
    '| findstr /i /v Coverage\\.profdata$ ' +
    '| findstr /i /v phpunit-code-coverage\\.xml$ ' +
    '| findstr /i /v coverage\\.serialized$ ' +
    '| findstr /i /v \\.pyc$ ' +
    '| findstr /i /v \\.cfg$ ' +
    '| findstr /i /v \\.egg$ ' +
    '| findstr /i /v \\.whl$ ' +
    '| findstr /i /v \\.html$ ' +
    '| findstr /i /v \\.js$ ' +
    '| findstr /i /v \\.cpp$ ' +
    '| findstr /i /v coverage\\.jade$ ' +
    '| findstr /i /v include\\.lst$ ' +
    '| findstr /i /v inputFiles\\.lst$ ' +
    '| findstr /i /v createdFiles\\.lst$ ' +
    '| findstr /i /v coverage\\.html$ ' +
    '| findstr /i /v scoverage\\.measurements\\..* ' +
    '| findstr /i /v test_.*_coverage\\.txt ' +
    '| findstr /i /v \\vendor\\ ' +
    '| findstr /i /v \\htmlcov\\ ' +
    '| findstr /i /v \\home\\cainus\\ ' +
    '| findstr /i /v \\js\\generated\\coverage\\ ' +
    '| findstr /i /v \\virtualenv\\ ' +
    '| findstr /i /v \\virtualenvs\\ ' +
    '| findstr /i /v \\\\.virtualenv\\ ' +
    '| findstr /i /v \\\\.virtualenvs\\ ' +
    '| findstr /i /v \\\\.env\\ ' +
    '| findstr /i /v \\\\.envs\\ ' +
    '| findstr /i /v \\env\\ ' +
    '| findstr /i /v \\envs\\ ' +
    '| findstr /i /v \\\\.venv\\ ' +
    '| findstr /i /v \\\\.venvs\\ ' +
    '| findstr /i /v \\venv\\ ' +
    '| findstr /i /v \\venvs\\ ' +
    '| findstr /i /v \\\\.git\\ ' +
    '| findstr /i /v \\\\.hg\\ ' +
    '| findstr /i /v \\\\.tox\\ ' +
    '| findstr /i /v \\__pycache__\\ ' +
    '| findstr /i /v \\\\.egg-info* ' +
    '| findstr /i /v \\\\$bower_components\\ ' +
    '| findstr /i /v \\node_modules\\ ' +
    '| findstr /i /v \\conftest_.*\\.c\\.gcov '
}

var sendToCodecovV2 = function(
  codecov_endpoint,
  query,
  upload_body,
  on_success,
  on_failure
) {
  // Direct to Codecov
  request(
    {
      uri: urlgrey(codecov_endpoint + '/upload/v2')
        .query(query)
        .toString(),
      method: 'POST',
      body: upload_body,
      headers: {
        'Content-Type': 'text/plain',
        Accept: 'text/plain',
      },
    },
    function(err, response) {
      if (err || response.statusCode !== 200) {
        console.log('    ' + (err || response.body))
        return response
          ? on_failure(response.statusCode, response.body)
          : on_failure(err.code, err.message)
      } else {
        console.log('    Success!')
        console.log('    View report at: ' + response.body)
        return on_success(response.body)
      }
    }
  )
}

var sendToCodecovV3 = function(
  codecov_endpoint,
  query,
  upload_body,
  on_success,
  on_failure
) {
  // Direct to S3
  request(
    {
      uri: urlgrey(codecov_endpoint + '/upload/v4')
        .query(query)
        .toString(),
      method: 'POST',
      body: '',
      headers: {
        'Content-Type': 'text/plain',
        Accept: 'text/plain',
      },
    },
    function(err, response, result) {
      if (err) {
        sendToCodecovV2(
          codecov_endpoint,
          query,
          upload_body,
          on_success,
          on_failure
        )
      } else {
        var codecov_report_url = result.split('\n')[0]
        request(
          {
            uri: result.split('\n')[1],
            method: 'PUT',
            body: upload_body,
            headers: {
              'Content-Type': 'text/plain',
              'x-amz-acl': 'public-read',
            },
          },
          function(err) {
            if (err) {
              sendToCodecovV2(
                codecov_endpoint,
                query,
                upload_body,
                on_success,
                on_failure
              )
            } else {
              console.log('    Success!')
              console.log('    View report at: ' + codecov_report_url)
              on_success(codecov_report_url)
            }
          }
        )
      }
    }
  )
}

var upload = function(args, on_success, on_failure) {
  // Build query
  var codecov_endpoint =
    args.options.url ||
    process.env.codecov_url ||
    process.env.CODECOV_URL ||
    'https://codecov.io'
  var query = {}
  var debug = []
  var yamlFile =
    args.options.yml ||
    process.env.codecov_yml ||
    process.env.CODECOV_YML ||
    'codecov.yml'

  console.log(
    '' +
      '  _____          _  \n' +
      ' / ____|        | |  \n' +
      '| |     ___   __| | ___  ___ _____   __  \n' +
      '| |    / _ \\ / _` |/ _ \\/ __/ _ \\ \\ / /  \n' +
      '| |___| (_) | (_| |  __/ (_| (_) \\ V /  \n' +
      ' \\_____\\___/ \\__,_|\\___|\\___\\___/ \\_/  \n' +
      '                                ' +
      version
  )

  if ((args.options.disable || '').split(',').indexOf('detect') === -1) {
    console.log('==> Detecting CI Provider')
    query = detectProvider()
  } else {
    debug.push('disabled detect')
  }

  query.yaml = [yamlFile, '.codecov.yml'].reduce(function(result, file) {
    return (
      result ||
      (fs.existsSync(path.resolve(process.cwd(), file))
        ? path.resolve(process.cwd(), file)
        : undefined)
    )
  }, undefined)

  if (args.options.build) {
    query.build = args.options.build
  }

  if (args.options.commit) {
    query.commit = args.options.commit
  }

  if (args.options.branch) {
    query.branch = args.options.branch
  }

  if (args.options.slug) {
    query.slug = args.options.slug
  }

  var flags =
    args.options.flags || process.env.codecov_flags || process.env.CODECOV_FLAGS
  if (flags) {
    query.flags = flags
  }

  var yamlToken
  try {
    var loadedYamlFile = jsYaml.safeLoad(fs.readFileSync(query.yaml, 'utf8'))
    yamlToken =
      loadedYamlFile && loadedYamlFile.codecov && loadedYamlFile.codecov.token
  } catch (e) {
    // silently fail
  }
  var token =
    args.options.token ||
    yamlToken ||
    process.env.codecov_token ||
    process.env.CODECOV_TOKEN
  if (token) {
    query.token = token
  }

  query.package = 'node-' + version

  console.log('==> Configuration: ')
  console.log('    Endpoint: ' + codecov_endpoint)
  // Don't output `query` directly as it contains the upload token
  console.log({
    commit: query.commit,
    branch: query.branch,
    package: query.package,
  })

  var upload = ''

  // Add specified env vars
  var env_found = false
  if (args.options.env || process.env.CODECOV_ENV || process.env.codecov_env) {
    var env = (
      args.options.env +
      ',' +
      (process.env.CODECOV_ENV || '') +
      ',' +
      (process.env.codecov_env || '')
    ).split(',')
    for (var i = env.length - 1; i >= 0; i--) {
      if (env[i]) {
        upload += env[i] + '=' + (process.env[env[i]] || '').toString() + '\n'
        env_found = true
      }
    }
    if (env_found) {
      upload += '<<<<<< ENV\n'
    }
  }

  // List git files
  var root = path.resolve(args.options.root || query.root || '.')
  console.log('==> Building file structure')
  try {
    upload +=
      execSync('git ls-files || hg locate', { cwd: root })
        .toString()
        .trim() + '\n<<<<<< network\n'
  } catch (err) {
    // not a git/hg dir, emulating git/hg ignore behavior
    upload +=
      walk
        .sync({ path: root, ignoreFiles: ['.gitignore', '.hgignore'] })
        .join('\n')
        .trim() + '\n<<<<<< network\n'
  }
  // Make gcov reports
  if ((args.options.disable || '').split(',').indexOf('gcov') === -1) {
    try {
      console.log('==> Generating gcov reports (skip via --disable=gcov)')
      var gcg = args.options['gcov-glob'] || ''
      if (gcg) {
        if (!isWindows) {
          gcg = gcg
            .split(' ')
            .map(function(p) {
              return "-not -path '" + p + "'"
            })
            .join(' ')
        } else {
          gcg = gcg
            .split(' ')
            .map(function(p) {
              return '^| findstr /i /v ' + p
            })
            .join(' ')
        }
      }
      var gcov
      if (!isWindows) {
        gcov =
          'find ' +
          (args.options['gcov-root'] || root) +
          " -type f -name '*.gcno' " +
          gcg +
          ' -exec ' +
          (args.options['gcov-exec'] || 'gcov') +
          ' ' +
          (args.options['gcov-args'] || '') +
          ' {} +'
      } else {
        // @TODO support for root
        // not straight forward due to nature of windows command dir
        gcov =
          'for /f "delims=" %g in (\'dir /a-d /b /s *.gcno ' +
          gcg +
          "') do " +
          (args.options['gcov-exec'] || 'gcov') +
          ' ' +
          (args.options['gcov-args'] || '') +
          ' %g'
      }
      debug.push(gcov)
      console.log('    $ ' + gcov)
      execSync(gcov)
    } catch (e) {
      console.log('    Failed to run gcov command.')
    }
  } else {
    debug.push('disabled gcov')
  }

  // Detect .bowerrc
  var bowerrc
  if (!isWindows) {
    bowerrc = execSync('test -f .bowerrc && cat .bowerrc || echo ""', {
      cwd: root,
    })
      .toString()
      .trim()
  } else {
    bowerrc = execSync('if exist .bowerrc type .bowerrc', { cwd: root })
      .toString()
      .trim()
  }
  if (bowerrc) {
    bowerrc = JSON.parse(bowerrc).directory
    if (bowerrc) {
      if (!isWindows) {
        more_patterns =
          " -not -path '*/" + bowerrc.toString().replace(/\/$/, '') + "/*'"
      } else {
        more_patterns =
          '| findstr /i /v \\' + bowerrc.toString().replace(/\/$/, '') + '\\'
      }
    }
  }

  var files = [],
    file = null
  if (args.options.pipe) {
    // Append piped reports
    upload += '# path=piped\n' + args.options.pipe.join('') + '\n<<<<<< EOF\n'
    console.log('==> Reading report from stdin')
  } else if (args.options.file) {
    // Append manually entered reports
    file = args.options.file
    console.log('==> Targeting specific file')
    try {
      upload +=
        '# path=' +
        file +
        '\n' +
        fs.readFileSync(file, 'utf8').toString() +
        '\n<<<<<< EOF\n'
      console.log('    + ' + file)
      files.push(file)
    } catch (e) {
      debug.push('failed: ' + file.split('/').pop())
      console.log('    X Failed to read file at ' + file)
    }
  } else if ((args.options.disable || '').split(',').indexOf('search') === -1) {
    console.log('==> Scanning for reports')
    var _files
    if (!isWindows) {
      _files = execSync('find ' + root + ' ' + patterns + more_patterns)
        .toString()
        .trim()
        .split('\n')
    } else {
      // @TODO support for a root directory
      // It's not straightforward due to the nature of the dir command
      _files = execSync('dir ' + patterns + more_patterns)
        .toString()
        .trim()
        .split('\r\n')
    }
    if (_files) {
      for (var i2 = _files.length - 1; i2 >= 0; i2--) {
        file = _files[i2]
        try {
          upload +=
            '# path=' +
            file +
            '\n' +
            fs.readFileSync(file, 'utf8').toString() +
            '\n<<<<<< EOF\n'
          console.log('    + ' + file)
          files.push(file)
        } catch (e) {
          debug.push('failed: ' + file.split('/').pop())
          console.log('    X Failed to read file at ' + file)
        }
      }
    }
  } else {
    debug.push('disabled search')
  }

  if (files) {
    // Upload to Codecov
    if (args.options.dump) {
      console.log('-------- DEBUG START --------')
      console.log(upload)
      console.log('-------- DEBUG END --------')
    } else {
      console.log('==> Uploading reports')
      var _upload
      if ((args.options.disable || '').split(',').indexOf('s3') === -1) {
        _upload = sendToCodecovV3
      } else {
        _upload = sendToCodecovV2
      }
      _upload(
        codecov_endpoint,
        query,
        upload,
        function() {
          // remove files after Uploading
          if (args.options.clear) {
            for (var i = files.length - 1; i >= 0; i--) {
              try {
                fs.unlinkSync(files[i])
              } catch (e) {}
            }
          }
          if (on_success) {
            on_success.apply(this, arguments)
          }
        },
        on_failure || function() {}
      )
    }
  }

  return {
    body: upload,
    files: files,
    query: query,
    debug: debug,
    url: codecov_endpoint,
  }
}

module.exports = {
  upload: upload,
  version: version,
  sendToCodecovV2: sendToCodecovV2,
  sendToCodecovV3: sendToCodecovV3,
}
