# frozen_string_literal: true

require "rake/clean"
require "logger"
require "json"
require "yaml"

%w[makit makit/v1 makit/cli makit/content makit/mp].each do |dir|
  Dir[File.join(__dir__, dir, "*.rb")].each do |file|
    require_relative file
  end
end

module Makit
  class Error < StandardError; end

  # Constants
  #PROJECT = Makit::V1::Project.create

  STARTTIME = Time.now
  IS_GIT_REPO = Dir.exist? ".git"
  DETACHED = `git status`.include?("detached")
  IS_READ_ONLY = !IS_GIT_REPO || DETACHED
  RUNTIME_IDENTIFIER = Makit::Environment::get_runtime_identifier
  DEVICE = Socket.gethostname
  LOGGER = Makit::Logging::MultiLogger.create_logger

  # Git Commit and Branch/Tag constants
  ENV["COMMIT_SHA"] = Makit::Git::commitsha
  ENV["COMMIT_BRANCH"] = Makit::Git::branch

  RUNNER = CommandRunner.new
  #RUNNER.log_to_artifacts = true
  # Variables
  log_level = Logger::INFO
  package_type = Makit::V1::PackageType::GEM
  commands = Makit::Commands.new

  # methods
  #
  # initialize a git repository
  def self.init(directory)
    if !Dir.exist?(directory)
      FileUtils.mkdir_p(directory)
    end
    raise Makit::Error.new("directory does not exist: #{directory}") if !Dir.exist?(directory)
    Dir.chdir(directory) do
      File.write(".gitignore", Makit::Content::GITIGNORE) unless File.exist?(".gitignore")
      init = Makit::RUNNER.execute "git init"
      if init.exit_code != 0
        raise Makit::Error.new("failed to initialize local repository: #{directory}\n#{Makit::Humanize.get_command_summary(init)}")
      end
      init
    end
  end

  # clone a git repository to a local directory in Directories::CLONE
  # returns the Makit::V1::Command for 'git clone ...'
  def self.clone(git_repository)
    commands = []
    # make sure a local clone of the repository exists
    clone_dir = Directories::get_clone_directory(git_repository)
    if (!Dir.exist?(clone_dir))
      commands << Makit::RUNNER.execute("git clone #{git_repository} #{clone_dir}")
    end
    commands
  end

  # pull the latest changes from the remote repository to a local clone in Directories::CLONE
  def self.pull(git_repository)
    clone_dir = Directories::get_clone_directory(git_repository)
    raise Makit::Error.new("clone directory does not exist: #{clone_dir}") if !Dir.exist?(clone_dir)
    Dir.chdir(clone_dir) do
      request = Makit::V1::CommandRequest.new(
        name: "git",
        arguments: ["pull"],
        directory: clone_dir,
      )
      pull_command = Makit::RUNNER.execute(request)
      raise Makit::Error.new(Makit::Humanize::get_command_details(pull_command)) if pull_command.exit_code != 0
      return pull_command
    end
  end

  def self.clone_or_pull(git_repository)
    commands = []
    clone_dir = Directories::get_clone_directory(git_repository)
    if Dir.exist?(clone_dir)
      commands << pull(git_repository)
    else
      commands << clone(git_repository)
    end
    commands
  end

  # log information about a specific repository
  # return an array of GitLogEntry objects
  def self.log(git_repository, limit, skip)
    entries = []
    clone_dir = Directories::get_clone_directory(git_repository)
    raise Makit::Error.new("clone directory does not exist: #{clone_dir}") if !Dir.exist?(clone_dir)
    Dir.chdir(clone_dir) do
      log_command = Makit::RUNNER.execute("git log -n #{limit} --skip #{skip} --date=iso")
      if log_command.exit_code != 0
        lines = log_command.stderr.split("\n")
        # iterate over the lines, generating a GitLogEntry for each commit
        lines.each do |line|
          if line.start_with?("commit")
            commit = line.split(" ")[1]
            entries << GitLogEntry.new(commit)
          end
          if line.start_with?("Author:")
            entries.last.author = line.split(" ")[1..-1].join(" ")
          end
          if line.start_with?("Date:")
            entries.last.date = line.split(" ")[1..-1].join(" ")
          end
          if line.start_with?("    ")
            entries.last.message += line[4..-1]
          end
        end
      end
    end
    entries
  end

  # work on a local clone of a git repository
  # if the repository does not exist, clone it
  # if the repository exists, pull the latest changes
  # if a build command can be found, execute it and return the result Makit::V1::WorkResult
  def self.work(repository)
    commands = []
    work_dir = Makit::Directories::get_work_directory(repository)
    commands << clone_or_pull(repository)
    clone_dir = Makit::Directories::get_clone_directory(repository)
    if !Dir.exist?(work_dir)
      # make the parent directory for work_dir if it does not exist
      FileUtils.mkdir_p(File.dirname(work_dir)) unless Dir.exist?(File.dirname(work_dir))
      Makit::RUNNER::execute "git clone #{clone_dir} #{work_dir}"
    end
    Dir.chdir(work_dir) do
      # if there is no .gitignore file, create one
      File.write(".gitignore", Makit::Content::GITIGNORE) unless File.exist?(".gitignore")
    end
    nil?
  end

  def self.enable_monkey_patch
    %w[makit/mp].each do |dir|
      Dir[File.join(__dir__, dir, "*.rb")].each do |file|
        require_relative file
      end
    end
  end
  # Given a git repository URL and a commit id, create a new MakeResult object.
  def self.make(url, commit, force = false)
    log_filename = File.join(Directories::get_log_directory(url), commit, +"#{RUNTIME_IDENTIFIER}.#{DEVICE}.json")
    if File.exist?(log_filename) && !force && commit != "latest"
      begin
        # deserialize the log file to a Makite::V1::MakeResult object
        make_result = Makit::V1::MakeResult.decode_json(File.read(log_filename))
        return make_result
      rescue => e
        # if deserialization fails, delete the log file and continue
        FileUtils.rm(log_filename)
      end
    else
      commands = []
      begin
        clone_or_pull(url).each do |command|
          commands << command
        end
        # make sure a local clone of the repository exists
        clone_dir = Directories::get_clone_directory(url)
        raise Makit::Error.new("clone directory does not exist: #{clone_dir}") if !Dir.exist?(clone_dir)

        if (commit == "latest")
          Dir.chdir(clone_dir) do
            git_log = Makit::RUNNER.execute("git log -n 1 --date=iso")

            commands << git_log
            # assert that the commit is valid
            commit = git_log.output.match(/^commit ([0-9a-f]{40})$/i)[1]
            raise Makit::Error.new("invalid commit: #{commit}") if commit.nil? || commit.empty? || !commit.match?(/\A[0-9a-f]{40}\z/i)
            log_filename = File.join(Directories::get_log_directory(url), commit, +"#{RUNTIME_IDENTIFIER}.#{DEVICE}.json")
          end
        end

        # clone a fresh copy of the repository to a make directory
        make_dir = Directories::get_make_commit_directory(url, commit)
        FileUtils.rm_rf(make_dir) if Dir.exist?(make_dir)
        commands << Makit::RUNNER.execute("git clone #{clone_dir} #{make_dir}")
        raise Makit::Error.new("failed to clone repository: #{url} to #{make_dir}") if !Dir.exist?(make_dir)
        Dir.chdir(make_dir) do
          commands << Makit::RUNNER.execute("git reset --hard #{commit}")
          commands << Makit::RUNNER.execute("git log -n 1")

          commands << Makit::RUNNER.execute("bundle install") if File.exist? "Gemfile"
          if File.exist? ("Rakefile")
            commands << Makit::RUNNER.execute("rake default")
          else
            commands << Makit::RUNNER.execute("rake default") if File.exist? "rakefile.rb"
          end

          make_result = Makit::V1::MakeResult.new(
            repository: url,
            commit: commit,
            branch: "?",
            tag: "?",
            device: DEVICE,
            runtime_identifier: RUNTIME_IDENTIFIER,
          )
          commands.flatten.each do |command|
            make_result.commands << command
          end

          # save the MakeResult object to a log file as pretty printed json
          FileUtils.mkdir_p(File.dirname(log_filename)) unless Dir.exist?(File.dirname(log_filename))
          File.write(log_filename, make_result.to_json)

          return make_result
        end
      rescue => e
        message = "error raised attempting to make repository: #{url} commit: #{commit}\n\n"
        message += "#{e.message}\n"
        backtrace = e.backtrace.join("\n")
        message += "#{backtrace}\n\n"
        message += "commands:\n"
        commands.flatten.each do |command|
          message += Makit::Humanize::get_command_details(command)
        end
        raise Makit::Error.new(message)
      end
    end
  end
end

if !File.exist?(".gitignore")
  Makit::LOGGER.info("added .gitignore file")
  File.open(".gitignore", "w") do |file|
    file.puts Makit::Content::GITIGNORE
  end
end