#!/usr/bin/env ruby
require "json"
require "net/http"
require "optparse"
require "pathname"

options = {
  code_assets_dir: "code",
  pipeline_assets_dir: "pipelines",
  root_dir: File.join(ENV["CODESPACE_VSCODE_FOLDER"], "azure_devops/bootstrap")
}

OptionParser.new do |opt|
  opt.on('--organization ORGANIZATION') { |o| options[:organization] = o }
  opt.on('--project PROJECT') { |o| options[:project] = o }
  opt.on('--access-token ACCESS_TOKEN') { |o| options[:access_token] = o }
end.parse!

[:organization, :project, :access_token].each do |key|
  raise OptionParser::MissingArgument, key if options[key].nil?
end

def post_request(uri, body, access_token)
  Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
    request = Net::HTTP::Post.new(uri)
    request.basic_auth "", access_token
    request.body = body.to_json
    request.content_type = 'application/json'
  
    response = http.request request

    yield(response) if block_given?

    sleep(5)

    JSON.parse(response.body)
  end
end

def create_project(options)
  organization = options[:organization]
  project = options[:project]
  access_token = options[:access_token]

  puts "Creating project #{project} in organization #{organization}..."
  uri = URI("https://dev.azure.com/#{organization}/_apis/projects?api-version=6.0")
  
  project_to_create = {
    name: project,
    description: "Project to be used for Valet Labs",
    capabilities: {
      versioncontrol: {
        sourceControlType: "Git"
      },
      processTemplate: {
        templateTypeId: "6b724908-ef14-45cf-84f8-768b5384da45"
      }
    }
  }

  post_request(uri, project_to_create, access_token) do |response|
    raise "Error creating project (#{response.code}:#{response.message})" unless response.code.start_with?("20")
  end
end

def create_repository(options)
  organization = options[:organization]
  project = options[:project]
  # use the default repository created when the project is created
  repository = options[:project]
  access_token = options[:access_token]
  root_dir = options[:root_dir]
  directories = [options[:code_assets_dir], options[:pipeline_assets_dir]]

  puts "Creating repository #{repository}..."
  uri = URI("https://dev.azure.com/#{organization}/#{project}/_apis/git/repositories/#{repository}/pushes?api-version=7.1-preview.2")
  
  root = Pathname.new(root_dir)
  repository_to_create = {
      refUpdates: [{
        name: "refs/heads/main",
        oldObjectId: "0000000000000000000000000000000000000000"
      }],
      commits: [{
        comment: "Initial commit.",
        changes: directories.map do |directory|
          Dir[File.join(root_dir, directory, "**/*")].reject {|f| File.directory?(f) }.map do |entry|
            {
              changeType: "add",
              item: {
                path: File.join("/", Pathname.new(entry).relative_path_from(root).to_s)
              },
              newContent: {
                content: File.read(entry),
                contentType: "rawText"
              }
            }
          end
        end.flatten
      }]
  }

  post_request(uri, repository_to_create, access_token) do |response|
    raise "Error creating repository (#{response.code}:#{response.message})" unless response.code.start_with?("20")
  end
end

def create_yaml_pipelines(options, repository)
  organization = options[:organization]
  project = options[:project]
  repository_name = repository.dig("repository", "name")
  repository_id = repository.dig("repository", "id")
  access_token = options[:access_token]
  root_dir = options[:root_dir]
  pipeline_assets_dir = options[:pipeline_assets_dir]

  puts "Creating yaml pipelines..."
 
  uri = URI("https://dev.azure.com/#{organization}/#{project}/_apis/pipelines?api-version=6.0-preview.1")
  root = Pathname.new(root_dir)
  Dir[File.join(root_dir, pipeline_assets_dir, "yml", "*")].each do |entry|
    puts "Creating pipeline #{entry}..."

    pipeline_to_create = {
      folder: "pipelines",
      name: File.basename(entry, ".yml"),
      configuration: {
        type: "yaml",
        path: File.join("/", Pathname.new(entry).relative_path_from(root).to_s),
        repository: {
          id: repository_id,
          name: repository_name,
          type: "azureReposGit"
        }
      }
    }

    post_request(uri, pipeline_to_create, access_token) do |response|
      raise "Error creating pipeline (#{response.code}:#{response.message})" unless response.code.start_with?("20")
    end
  end
end

def create_classic_pipelines(options, repository)
  organization = options[:organization]
  project = options[:project]
  repository_name = repository.dig("repository", "name")
  repository_id = repository.dig("repository", "id")
  access_token = options[:access_token]
  root_dir = options[:root_dir]
  pipeline_assets_dir = options[:pipeline_assets_dir]

  puts "Creating classic pipelines..."
 
  uri = URI("https://dev.azure.com/#{organization}/#{project}/_apis/build/definitions?api-version=7.1-preview.7")
  root = Pathname.new(root_dir)
  Dir[File.join(root_dir, pipeline_assets_dir, "classic", "*")].each do |entry|
    puts "Creating pipeline #{entry}..."

    pipeline_to_create = JSON.parse(File.read(entry))

    pipeline_to_create["repository"]["name"] = repository_name
    pipeline_to_create["repository"]["id"] = repository_id

    post_request(uri, pipeline_to_create, access_token) do |response|
      raise "Error creating pipeline (#{response.code}:#{response.message})" unless response.code.start_with?("20")
    end
  end
end


create_project(options)
repository = create_repository(options)
create_yaml_pipelines(options, repository)
create_classic_pipelines(options, repository)

puts "Success!"