module Fastlane
  module Actions
    module SharedValues
      IPA_OUTPUT_PATH ||= :IPA_OUTPUT_PATH
      PKG_OUTPUT_PATH ||= :PKG_OUTPUT_PATH
      DSYM_OUTPUT_PATH ||= :DSYM_OUTPUT_PATH
      XCODEBUILD_ARCHIVE ||= :XCODEBUILD_ARCHIVE # originally defined in XcodebuildAction
    end

    class BuildAppAction < Action
      # rubocop:disable Metrics/PerceivedComplexity
      def self.run(values)
        require 'gym'

        unless Actions.lane_context[SharedValues::SIGH_PROFILE_TYPE].to_s == "development"
          values[:export_method] ||= Actions.lane_context[SharedValues::SIGH_PROFILE_TYPE]
        end

        if Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING]
          # Since Xcode 9 you need to explicitly provide the provisioning profile per app target
          # If the user is smart and uses match and gym together with fastlane, we can do all
          # the heavy lifting for them
          values[:export_options] ||= {}
          # It's not always a hash, because the user might have passed a string path to a ready plist file
          # If that's the case, we won't set the provisioning profiles
          # see https://github.com/fastlane/fastlane/issues/9490
          if values[:export_options].kind_of?(Hash)
            match_mapping = (Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING] || {}).dup
            existing_mapping = (values[:export_options][:provisioningProfiles] || {}).dup

            # Be smart about how we merge those mappings in case there are conflicts
            mapping_object = Gym::CodeSigningMapping.new
            hash_to_use = mapping_object.merge_profile_mapping(primary_mapping: existing_mapping,
                                                             secondary_mapping: match_mapping,
                                                                export_method: values[:export_method])

            values[:export_options][:provisioningProfiles] = hash_to_use
          else
            self.show_xcode_9_warning
          end
        elsif Actions.lane_context[SharedValues::SIGH_PROFILE_PATHS]
          # Since Xcode 9 you need to explicitly provide the provisioning profile per app target
          # If the user used sigh we can match the profiles from sigh
          values[:export_options] ||= {}
          if values[:export_options].kind_of?(Hash)
            # It's not always a hash, because the user might have passed a string path to a ready plist file
            # If that's the case, we won't set the provisioning profiles
            # see https://github.com/fastlane/fastlane/issues/9684
            values[:export_options][:provisioningProfiles] ||= {}
            Actions.lane_context[SharedValues::SIGH_PROFILE_PATHS].each do |profile_path|
              begin
                profile = FastlaneCore::ProvisioningProfile.parse(profile_path)
                app_id_prefix = profile["ApplicationIdentifierPrefix"].first
                entitlements = profile["Entitlements"]
                bundle_id = (entitlements["application-identifier"] || entitlements["com.apple.application-identifier"]).gsub("#{app_id_prefix}.", "")
                values[:export_options][:provisioningProfiles][bundle_id] = profile["Name"]
              rescue => ex
                UI.error("Couldn't load profile at path: #{profile_path}")
                UI.error(ex)
                UI.verbose(ex.backtrace.join("\n"))
              end
            end
          else
            self.show_xcode_9_warning
          end
        end

        gym_output_path = Gym::Manager.new.work(values)
        if gym_output_path.nil?
          UI.important("No output path received from gym")
          return nil
        end

        absolute_output_path = File.expand_path(gym_output_path)

        # Binary path
        if File.extname(absolute_output_path) == ".ipa"
          absolute_dsym_path = absolute_output_path.gsub(/.ipa$/, ".app.dSYM.zip")

          Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] = absolute_output_path
          ENV[SharedValues::IPA_OUTPUT_PATH.to_s] = absolute_output_path # for deliver
        elsif File.extname(absolute_output_path) == ".pkg"
          absolute_dsym_path = absolute_output_path.gsub(/.pkg$/, ".dSYM.zip")

          Actions.lane_context[SharedValues::PKG_OUTPUT_PATH] = absolute_output_path
          ENV[SharedValues::PKG_OUTPUT_PATH.to_s] = absolute_output_path # for deliver
        end

        # xcarchive path
        Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE] = Gym::BuildCommandGenerator.archive_path

        # dSYM path
        if absolute_dsym_path && File.exist?(absolute_dsym_path)
          Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH] = absolute_dsym_path
          ENV[SharedValues::DSYM_OUTPUT_PATH.to_s] = absolute_dsym_path
        end

        return absolute_output_path
      end
      # rubocop:enable Metrics/PerceivedComplexity

      def self.description
        "Easily build and sign your app (via _gym_)"
      end

      def self.details
        "More information: https://fastlane.tools/gym"
      end

      def self.output
        [
          ['IPA_OUTPUT_PATH', 'The path to the newly generated ipa file'],
          ['PKG_OUTPUT_PATH', 'The path to the newly generated pkg file'],
          ['DSYM_OUTPUT_PATH', 'The path to the dSYM files'],
          ['XCODEBUILD_ARCHIVE', 'The path to the xcodebuild archive']
        ]
      end

      def self.return_value
        "The absolute path to the generated ipa file"
      end

      def self.return_type
        :string
      end

      def self.author
        "KrauseFx"
      end

      def self.available_options
        require 'gym'
        Gym::Options.available_options
      end

      def self.is_supported?(platform)
        [:ios, :mac].include?(platform)
      end

      def self.example_code
        [
          'build_app(scheme: "MyApp", workspace: "MyApp.xcworkspace")',
          'build_app(
            workspace: "MyApp.xcworkspace",
            configuration: "Debug",
            scheme: "MyApp",
            silent: true,
            clean: true,
            output_directory: "path/to/dir", # Destination directory. Defaults to current directory.
            output_name: "my-app.ipa",       # specify the name of the .ipa file to generate (including file extension)
            sdk: "iOS 11.1"                  # use SDK as the name or path of the base SDK when building the project.
          )',
          'gym              # alias for "build_app"',
          'build_ios_app    # alias for "build_app (only iOS options)"',
          'build_mac_app    # alias for "build_app (only macOS options)"'
        ]
      end

      def self.category
        :building
      end

      def self.show_xcode_9_warning
        return unless Helper.xcode_at_least?("9.0")
        UI.message("You passed a path to a custom plist file for exporting the binary.")
        UI.message("Make sure to include information about what provisioning profiles to use with Xcode 9")
        UI.message("More information: https://docs.fastlane.tools/codesigning/xcode-project/#xcode-9-and-up")
      end
    end
  end
end
