# typed: false
# frozen_string_literal: true

require "rails/generators"
require "rails/generators/rails/app/app_generator"
require "tempfile"

module Hephaestus
  class AppGenerator < Rails::Generators::AppGenerator
    include ExitOnFailure

    hide!

    class_option :version,
      type: :boolean,
      aliases: "-v",
      group: :hephaestus,
      desc: "Show Hephaestus version number and quit"

    class_option :help,
      type: :boolean,
      aliases: "-h",
      group: :hephaestus,
      desc: "Show this help message and quit"

    def finish_template
      invoke(:hephaestus_customization)
      super
    end

    def hephaestus_customization
      say(set_color("Invoking Hephaestus customizations...", :cyan))

      run("rails app:update:bin")
      invoke(:customize_gemfile)

      invoke(:copy_github_actions)
      invoke(:copy_vscode_settings)

      invoke(:setup_development_environment)
      invoke(:setup_production_environment)
      invoke(:setup_staging_environment)
      invoke(:setup_test_environment)
      invoke(:setup_shared_environment_logic)

      invoke(:configure_app)
      invoke(:create_github_repo)
    end

    def customize_gemfile
      say(set_color("Customizing Gemfile...", :cyan))
      build(:replace_gemfile)
      capture_stdout do
        bundle_command("install")
        bundle_command("lock --add-platform x86_64-linux")
      end
    end

    def copy_github_actions
      say(set_color("Copying GitHub Actions...", :cyan))

      capture_stdout do
        directory(Hephaestus.source_path(".github"), ".github")
      end
    end

    def copy_vscode_settings
      say(set_color("Copying .vscode/...", :cyan))

      capture_stdout do
        directory(Hephaestus.source_path(".vscode"), ".vscode")
      end
    end

    def setup_development_environment
      say(set_color("Setting up the development environment...", :cyan))
      capture_stdout do
        build(:raise_on_delivery_errors)
        build(:configure_dev_hosting)
        build(:copy_setup_scripts)
      end
    end

    def setup_production_environment
      say(set_color("Setting up the production environment...", :cyan))
      capture_stdout do
        build(:setup_asset_host)
        build(:setup_ssl)
      end
    end

    def setup_staging_environment
      say(set_color("Setting up the staging environment...", :cyan))
      capture_stdout do
        build(:setup_staging_environment)
      end
    end

    def setup_test_environment
      say(set_color("Setting up the test environment...", :cyan))
      capture_stdout do
        build(:setup_test_environment)
      end
    end

    def setup_shared_environment_logic
      capture_stdout do
        build(:setup_background_worker)
        build(:setup_slack_logger)
      end
    end

    def configure_app
      say(set_color("Configuring app...", :cyan))

      remove_dir("app/assets")
      remove_dir("app/helpers")
      remove_dir("app/models")
      remove_dir("app/views/layouts")
      remove_dir("lib/assets")
      remove_dir("lib/tasks/.keep")
      remove_dir("test/helpers")
      remove_dir("test/channels")
      remove_dir("test/models")

      build(:configure_time_formats)

      bundle_command("binstubs bundler")

      capture_stdout do
        generate("hephaestus:core")
        generate("hephaestus:deployment")
        generate("hephaestus:lib")

        # keep this at the end
        generate("hephaestus:config")
      end
    end

    def create_github_repo
      if !options[:skip_git] && options[:github]
        say(set_color("Creating repository via `gh repo`...", :cyan))
        build(:create_github_repo, options[:github])
      end
    end

    # NOTE: this function name is important as it overrides
    # a Rails function of the same name (that function performs work
    # that we don't want to do)
    def leftovers
      build(:replace_generic_variables)

      say(set_color("Generating `hephaestus:license`...", :cyan))
      capture_stdout do
        generate("hephaestus:license")
      end
      say(set_color("Generating `hephaestus:rubocop`...", :cyan))
      capture_stdout do
        generate("hephaestus:rubocop")
      end
      say(set_color("Generating `hephaestus:sorbet`...", :cyan))
      capture_stdout do
        generate("hephaestus:sorbet")
      end
      say(set_color("Creating first commit...", :cyan))
      capture_stdout do
        invoke(:commit)
      end

      build(:restore_gemfile)
      invoke(:outro)
    end

    def commit
      run("git add .")
      run("git commit -m 'Initial commit'", capture: true)
    end

    def outro
      say(<<~OUTPUT)

        #{set_color("Congratulations!", :green)} You just made a Yetto plug.

        You'll need to `cd #{ARGV.first}` and run the following commands--I can't do it for you!

        * `bin/rails credentials:edit --environment development`

        #{set_color("WAIT!", :red)} After running this command, note that Rails credited a file called development.key in the config/credentials directory.
        Store this in 1Password, then change the .env file to use this value. Generate keys for staging/production, too:

        * `bin/rails credentials:edit --environment staging`
          `bin/rails credentials:edit --environment production`

        Store this in 1Password too; you don't need the .env file for staging/production.

        For `SIGNING_SECRET` and `YETTO_PLUG_ID`, you'll need to generate those values yourself.

        Then, try running `bin/rails c` to ensure that everything was set up correctly.
        Running `rake test` is also a good idea.

        #{set_color("Also, after pushing to GitHub:", :yellow)}

        * Set `settings/branches` to protect `production`
          * ✅ Require status checks to pass before merging
          * 🖊️ Status checks that are required: `test`, `ruby / brakeman`, `ruby / bundle-audit`.
            You can only set those 👆 after you've opened the first PR on GitHub. Crazy, I know!!
            But, doing so is *essential* for automerge to function properly.

        * Speaking of which, in `/settings`
          * ✅ Allow auto-merge
          * ✅ Automatically delete head branches
      OUTPUT
    end

    class << self
      def banner
        "hephaestus #{arguments.map(&:usage).join(" ")} [options]"
      end
    end

    protected

    def get_builder_class # rubocop:disable Naming/AccessorMethodName
      Hephaestus::AppBuilder
    end

    def using_active_record?
      !options[:skip_active_record]
    end

    private def capture_stdout
      stream = "stdout"
      captured_stream = Tempfile.new(stream)
      stream_io = eval("$#{stream}", binding, __FILE__, __LINE__)
      origin_stream = stream_io.dup
      stream_io.reopen(captured_stream)

      yield

      stream_io.rewind
      captured_stream.read
    ensure
      unless captured_stream.nil?
        captured_stream.close
        captured_stream.unlink
      end
      stream_io.reopen(origin_stream) unless stream_io.nil?
    end
  end
end