require "scaffolding"
require "scaffolding/file_manipulator"

require "faraday"
require "tempfile"

namespace :bullet_train do
  namespace :api do
    desc "Bump the current version of application's API"
    task :bump_version do
      # Calculate new version.
      initializer_content = File.new("config/initializers/api.rb").readline
      previous_version = initializer_content.scan(/v\d+/).pop
      new_version_int = previous_version.scan(/\d+/).pop.to_i + 1
      new_version = "v#{new_version_int}"

      # Update initializer.
      File.write("config/initializers/api.rb", initializer_content.gsub(previous_version, new_version))

      [
        "app/controllers/api/#{new_version}",
        "app/views/api/#{new_version}",
        "test/controllers/api/#{new_version}"
      ].each do |dir|
        Dir.mkdir(dir)
      end

      files_to_update = [
        "config/routes/api/#{previous_version}.rb",
        Dir.glob("app/controllers/api/#{previous_version}/**/*.rb") +
          Dir.glob("app/views/api/#{previous_version}/**/*.json.jbuilder") +
          Dir.glob("test/controllers/api/#{previous_version}/**/*.rb")
      ].flatten

      files_to_update.each do |file_name|
        previous_file_contents = File.open(file_name).readlines
        new_file_name = file_name.gsub(previous_version, new_version)

        updated_file_contents = previous_file_contents.map do |line|
          if line.match?(previous_version)
            line.gsub(previous_version, new_version)
          else
            line.gsub("Api::#{previous_version.upcase}", "Api::#{new_version.upcase}")
          end
        end

        # We can't create new files unless each directory under #{api/previous_version}
        # has been created under the new api directory. For example, we have to create
        # the `projects` directory before we can create the file `api/v2/projects/pages.json.jbuilder.`
        new_version_dir, dir_hierarchy = new_file_name.split(/(?<=#{new_version})\//)
        if dir_hierarchy.present? && dir_hierarchy.match?("/")
          dir_hierarchy = dir_hierarchy.split("/")
          dir_hierarchy.inject(new_version_dir) do |base, child_dir_or_file|
            # Stop making new directories if the string has an extention like ".rb"
            break if child_dir_or_file.match?(/\./)

            new_hierarchy = "#{base}/#{child_dir_or_file}"
            Dir.mkdir(new_hierarchy) unless Dir.exist?(new_hierarchy)
            new_hierarchy
          end
        end
        Scaffolding::FileManipulator.write(new_file_name, updated_file_contents)
      end

      # Here we make sure config/api/#{new_version}.rb is called from within the main routes file.
      previous_file_contents = File.open("config/routes.rb").readlines
      updated_file_contents = previous_file_contents.map do |line|
        if line.match?("draw \"api/#{previous_version}\"")
          new_version_draw_line = line.gsub(previous_version, new_version)
          line + new_version_draw_line
        else
          line
        end
      end
      Scaffolding::FileManipulator.write("config/routes.rb", updated_file_contents)

      # Update application locale for each locale that exists.
      I18n.available_locales.each do |lang|
        file = "config/locales/#{lang}/application.#{lang}.yml"
        transformer = Scaffolding::Transformer.new("", "")

        if File.exist?(file)
          transformer.add_line_to_file(
            file,
            "#{new_version_int}: #{new_version.upcase}",
            Scaffolding::Transformer::RUBY_NEW_API_VERSION_HOOK,
            prepend: true
          )
        end
      end

      puts "Finished bumping to #{new_version}"
    end

    desc "Push OpenAPI yaml file to Redocly"
    task push_to_redocly: :environment do
      include Rails.application.routes.url_helpers

      raise "You need to set REDOCLY_ORGANIZATION_ID in your environment. You can fetch it from the URL when you're on your Redocly dashboard." unless ENV["REDOCLY_ORGANIZATION_ID"].present?
      raise "You need to set REDOCLY_API_KEY in your environment. You can create one at https://app.redocly.com/org/#{ENV["REDOCLY_ORGANIZATION_ID"]}/settings/api-keys ." unless ENV["REDOCLY_API_KEY"].present?

      # Create a new Faraday connection
      conn = Faraday.new(api_url(version: BulletTrain::Api.current_version))

      # Fetch the file
      response = conn.get

      # Check if the request was successful
      if response.status == 200
        # Create a temp file
        temp_file = Tempfile.new(["openapi-", ".yaml"])

        # Write the file content to the temp file
        temp_file.binmode
        temp_file.write(response.body)
        temp_file.rewind

        # Close and delete the temp file when the script exits
        temp_file.close
        puts "File downloaded and saved to: #{temp_file.path}"

        puts `echo "#{ENV["REDOCLY_API_KEY"]}" | redocly login`

        puts `redocly push #{temp_file.path} "@#{ENV["REDOCLY_ORGANIZATION_ID"]}/#{I18n.t("application.name")}@#{BulletTrain::Api.current_version}" --public --upsert`

        temp_file.unlink
      else
        puts "Failed to download the OpenAPI Document. Status code: #{response.status}"
      end
    end

    desc "Export the OpenAPI schema for the application"
    task export_openapi_schema: :environment do
      @version = BulletTrain::Api.current_version
      dir = "tmp/openapi"
      Dir.mkdir(dir) unless File.exist?(dir)
      File.open("#{dir}/openapi-#{Time.now.strftime("%Y%m%d-%H%M%S")}.yaml", "w+") do |f|
        f.binmode
        f.write(
          ApplicationController.renderer.render(
            template: "api/#{@version}/open_api/index",
            layout: false,
            format: :text,
            assigns: {version: @version}
          )
        )
      end
    end

    desc "Create `api_title` and `api_description` translations"
    task create_translations: :environment do
      # Define the root of the locales directory
      locales_root = Rails.root.join("config", "locales").to_s

      # Define the backup directory
      backup_dir = Rails.root.join("tmp", "locales_backup")
      FileUtils.mkdir_p(backup_dir) unless Dir.exist?(backup_dir)

      original_files = Dir.glob("#{locales_root}/**/*.yml")
      differing_files_count = 0

      original_files.each do |file|
        puts "Processing #{file}..."

        lines = File.readlines(file)
        new_content = []
        inside_fields = false
        current_field = nil
        fields_indentation = 4
        api_title_exists = false
        api_description_exists = false

        # Read the entire file as a string
        lines.each_with_index do |line, index|
          # fields are always under locale:model:
          if line.start_with?("#{" " * fields_indentation}fields:")
            inside_fields = true
            new_content << line
            next
          end

          if inside_fields
            current_indentation = line.index(/\S/) || 0
            if current_indentation <= fields_indentation
              # We've exited the fields block
              inside_fields = false
              current_field = nil
            elsif current_indentation == fields_indentation + 2
              # We're on a line with a field name
              current_field = line.strip.split(":").first

              api_title_exists = false
              api_description_exists = false

              n = 1
              until index + n >= lines.length || (lines[index + n].index(/\S/) || 0) <= fields_indentation
                if lines[index + n].strip.start_with?("api_title:")
                  api_title_exists = true
                elsif lines[index + n].strip.start_with?("api_description:")
                  api_description_exists = true
                end
                n += 1
              end

            elsif current_field && line.strip.start_with?("heading:")
              heading_value = line.split(":").last&.strip
              indent = " " * current_indentation

              if heading_value&.start_with?("&", "*")
                new_content << line
              else
                heading_value = "&#{current_field} #{heading_value}"
                new_content << "#{indent}heading: #{heading_value}\n"
              end

              field_key = heading_value[1..]&.split(" ")&.first
              new_content << "#{indent}api_title: *#{field_key}\n" unless api_title_exists
              new_content << "#{indent}api_description: *#{field_key}\n" unless api_description_exists

              next
            end
          end

          new_content << line
        end

        # Only write to file if there are changes
        unless lines.join == new_content.join
          differing_files_count += 1

          # Compute the relative path manually
          relative_path = file.sub(/^#{Regexp.escape(locales_root)}\/?/, "")

          backup_path = File.join(backup_dir, relative_path)
          FileUtils.mkdir_p(File.dirname(backup_path))
          FileUtils.cp(file, backup_path)
          puts "↳ Updating, backup created: #{backup_path}"

          # Write the updated data back to the file
          File.write(file, new_content.join)
        end
      end

      puts "---"
      puts "Total files updated: #{differing_files_count}/#{original_files.size}"
    end
  end
end
