# fastlane ios release \
#   key_id:77HLG2C29P \
#   issuer_id:a1ead579-73d7-4227-b9d6-1aeccf17edb4 \
#   key_filepath:AuthKey_77HLG2C29P.p8 \
#   app_identifier:com.fragorus.live \
#   team_id:785R8UTSWS

# install fastlane plugin
# fastlane add_plugin versioning

default_platform(:ios)
platform :ios do
  before_all do
    ENV['MATCH_PASSWORD'] = '123456'
  end

  # print given options
  desc "Print given options"
  lane :print_options do |options|
    UI.message("Options: #{options}")
  end

  desc "Release a new version"
  lane :release do |options|
    # Group 1: Input validation and setup
    validate_required_options(options)
    verify_p8_file_exists(options[:key_filepath])
    api_key = setup_app_store_connect_api_key(options)

    # Group 2: App and bundle verification
    verify_bundle_id_exists(api_key, options[:app_identifier])
    verify_app_exists(api_key, options[:app_identifier])

    # Group 3: Version management
    new_version = determine_app_version(api_key, options[:app_identifier])
    new_build_number = get_next_build_number(api_key, options[:app_identifier], new_version)
    update_version_and_build_number(new_version, new_build_number)

    # stop the lane
    # next


    # Group 4: Certificate and signing setup
    keychain_name, keychain_password = setup_keychain(options[:app_identifier])
    cleanup_apple_signing_artifacts(api_key, options)
    setup_code_signing(api_key, options, keychain_name, keychain_password)

    # Group 5: Build and upload
    build_app(options[:app_identifier])
    upload_to_app_store(api_key, options[:team_id], options[:app_identifier])

    # Cleanup
    delete_keychain(name: "#{keychain_name}-db")
  end

  # Helper method to get next build number
  def get_next_build_number(api_key, app_identifier, version)
    begin
      # Check if we already have a build on TestFlight for this version
      has_build = false
      begin
        builds = Spaceship::ConnectAPI::Build.all(
          app_id: Spaceship::ConnectAPI::App.find(app_identifier).id,
          version: version
        )
        has_build = builds.size > 0
      rescue => e
        UI.message("Error checking builds: #{e.message}")
        has_build = false
      end

      if has_build
        # There are builds, so use fastlane's method to get the latest
        latest_build = latest_testflight_build_number(
          api_key: api_key,
          app_identifier: app_identifier,
          version: version
        )
        UI.message("Found existing builds. Latest build number: #{latest_build}")
        return latest_build + 1
      else
        # No builds found, so this is the first one
        UI.message("No builds found for version #{version}. Using build number 1.")
        return 1
      end
    rescue => e
      UI.error("Error in get_next_build_number: #{e.message}")
      UI.message("Defaulting to build number 1")
      return 1
    end
  end

  # Helper methods for improved readability
  def validate_required_options(options)
    required_options = %i[key_id issuer_id key_filepath app_identifier team_id]
    missing_options = required_options.select { |option| options[option].nil? }
    UI.user_error!("Missing required options: #{missing_options.join(', ')}") unless missing_options.empty?
  end

  def verify_p8_file_exists(key_filepath)
    unless File.exist?("../#{key_filepath}")
      UI.user_error!("The .p8 file does not exist in the parent folder (should be in the ios folder)")
    end
  end

  def setup_app_store_connect_api_key(options)
    return app_store_connect_api_key(
      key_id: options[:key_id],
      issuer_id: options[:issuer_id],
      key_filepath: options[:key_filepath],
      in_house: false
    )
  end

  def verify_bundle_id_exists(api_key, app_identifier)
    begin
      bundle_id = Spaceship::ConnectAPI::BundleId.find(app_identifier)
      
      if bundle_id.nil?
        UI.important("😕 Bundle ID verification failed")
        UI.user_error!("Bundle ID '#{app_identifier}' not found in developer account. Please create it first.")
      else
        UI.success("✅ Bundle ID '#{app_identifier}' verified successfully")
      end
    rescue => e
      UI.error("❌ Bundle ID verification error")
      UI.user_error!("Failed to verify bundle ID: #{e.message}")
    end
  end

  def verify_app_exists(api_key, app_identifier)
    begin
      app = Spaceship::ConnectAPI::App.find(app_identifier)
      
      if app.nil?
        UI.important("😕 App verification failed")
        UI.user_error!("App '#{app_identifier}' not found in developer account. Please create it first.")
      else
        UI.success("✅ App '#{app_identifier}' verified successfully")
      end
    rescue => e
      UI.error("❌ App verification error")
      UI.user_error!("Failed to verify app: #{e.message}")
    end
  end

  def determine_app_version(api_key, app_identifier)
    begin
      # Get the current live version from App Store
      current_live_version = get_app_store_version_number(
        bundle_id: app_identifier
      )
      UI.message("Current App Store live version: #{current_live_version}")

      # Check App Store state using Spaceship
      begin
        app_store_build_info = Spaceship::ConnectAPI::App.find(app_identifier)
          .get_app_store_versions
          .find { |version| version.version_string == current_live_version }

        app_state = app_store_build_info&.app_store_state
        UI.message("Current version state: #{app_state}")

        if app_state == "READY_FOR_SALE" || app_state == "PENDING_DEVELOPER_RELEASE"
          # If version is approved/released, increment to a new version
          version_components = current_live_version.split('.')
          if version_components.length >= 3
            # Increment patch version (1.2.3 -> 1.2.4)
            version_components[-1] = (version_components[-1].to_i + 1).to_s
          elsif version_components.length == 2
            # If only major.minor, add patch version (1.2 -> 1.2.1)
            version_components << "1"
          else
            # Fallback - just add .1
            current_live_version = "#{current_live_version}.1"
          end

          new_version = version_components.join('.')
          UI.message("Incrementing version to: #{new_version}")
        else
          # Keep the same version
          new_version = current_live_version
        end
      rescue => e
        UI.message("Error checking app state: #{e.message}. Using current version.")
        new_version = current_live_version
      end
      return new_version
    rescue => e
      # If app is not in App Store yet, use default version 1.0.0
      UI.message("App not found in App Store: #{e.message}. Using initial version.")
      return "1.0.0"
    end
  end

  def update_version_and_build_number(new_version, new_build_number)
    UI.message("Using version: #{new_version} with build number: #{new_build_number}")

    increment_version_number_in_plist(
      version_number: new_version
    )

    increment_build_number_in_plist(
      build_number: new_build_number.to_s
    )
  end

  def setup_keychain(app_identifier)
    keychain_name = app_identifier
    keychain_password = "123456"
    unless File.exist?("~/Library/Keychains/#{keychain_name}-db")
      create_keychain(
        name: keychain_name,
        password: keychain_password,
        default_keychain: false,
        unlock: true,
        timeout: 0,
        lock_when_sleeps: false,
        add_to_search_list: true
      )
    end
    sh "security unlock-keychain -p #{keychain_password} ~/Library/Keychains/#{keychain_name}-db"
    return [keychain_name, keychain_password]
  end

  def cleanup_apple_signing_artifacts(api_key, options)
    app_id = options[:app_identifier]
    UI.message("🧹 Cleaning up Apple signing artifacts for '#{app_id}'...")

    # 1) Prune one distribution cert if there are 2 or more non-expired ones.
    now = Time.now
    dist_certs = Spaceship::ConnectAPI::Certificate.all.select do |c|
      c.certificate_type == "DISTRIBUTION" &&
        (c.expiration_date.nil? || Time.parse(c.expiration_date) > now)
    end
    UI.message("Found #{dist_certs.count} non-expired DISTRIBUTION certificate(s)")
    if dist_certs.count >= 2
      target = dist_certs.first
      UI.important("Deleting distribution cert '#{target.name}' (id: #{target.id})")
      target.delete!
    else
      UI.success("✅ Cert count under threshold — no deletion needed")
    end

    # 2) Delete the existing match-generated AppStore profile so match regenerates a fresh one.
    profile_name = "match AppStore #{app_id}"
    profile = Spaceship::ConnectAPI::Profile.all.find { |p| p.name == profile_name }
    if profile
      UI.important("Deleting provisioning profile '#{profile_name}' (id: #{profile.id})")
      profile.delete!
    else
      UI.success("✅ Provisioning profile '#{profile_name}' not found — nothing to delete")
    end
  end

  def setup_code_signing(api_key, options, keychain_name, keychain_password)
    branch_name = options[:app_identifier]
    match_git_url = ENV['GIT_CERTIFICATES_URL'] || "file://#{ENV['HOME']}/.match-certs/#{options[:app_identifier]}.git"

    match(
      type: "appstore",
      api_key: api_key,
      git_url: match_git_url,
      app_identifier: options[:app_identifier],
      force: true,
      force_for_new_certificates: true,
      keychain_name: "#{keychain_name}-db",
      keychain_password: keychain_password,
      git_branch: branch_name
    )

    update_code_signing_settings(
      use_automatic_signing: false,
      path: "Runner.xcodeproj",
      team_id: options[:team_id],
      bundle_identifier: options[:app_identifier],
      code_sign_identity: "iPhone Distribution",
      sdk: "iphoneos*",
      profile_name: "match AppStore #{options[:app_identifier]}"
    )
  end

  def build_app(app_identifier)
    gym(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          app_identifier => "match AppStore #{app_identifier}"
        }
      },
      derived_data_path: "build",
      buildlog_path: "build",
    )
  end

  def upload_to_app_store(api_key, team_id, app_identifier, depth = 0)
    max_retries = 3
    
    # Prevent potential infinite recursion
    if depth >= max_retries
      UI.error("Maximum retry depth (#{max_retries}) reached. Please manually increment your build number and try again.")
      return false
    end
    
    begin
      current_build = get_build_number_from_plist(xcodeproj: 'Runner.xcodeproj')
      UI.message("Starting app upload with build number: #{current_build}")

      deliver(
        api_key: api_key,
        ipa: "Runner.ipa",
        team_id: team_id,
        app_identifier: app_identifier,
        precheck_include_in_app_purchases: false,
        submit_for_review: false,
        automatic_release: false,
        skip_metadata: true,
        skip_screenshots: true,
        skip_binary_upload: false
      )
      
      UI.success("📱 App successfully uploaded to App Store Connect")
      return true
    rescue FastlaneCore::Interface::FastlaneError => e
      UI.error("Deliver error: #{e.message}")

      if e.message.include?("already been used") ||
         e.message.include?("must be higher") ||
         e.message.include?("higher than the previously uploaded version") ||
         e.message.include?("value that has already been used") ||
         e.message.include?("already exists")

        # Get current build number
        current_build = get_build_number_from_plist(xcodeproj: 'Runner.xcodeproj').to_i
        
        # Calculate new build number with exponential backoff strategy
        increment_amount = 5 * (2 ** depth)  # 5, 10, 20 for retries 0,1,2
        new_build = current_build + increment_amount
        
        UI.important("Build number conflict detected (attempt #{depth+1}/#{max_retries}).")
        UI.important("Incrementing build number from #{current_build} to #{new_build} and rebuilding...")

        # Update build number
        increment_build_number_in_plist(build_number: new_build.to_s)

        # Rebuild the app with new build number
        begin
          build_app(app_identifier)
        rescue => build_error
          UI.error("Failed to rebuild app: #{build_error.message}")
          return false
        end

        # Try uploading again with incremented depth parameter to track recursion
        UI.message("Retrying upload with new build number: #{new_build}")
        return upload_to_app_store(api_key, team_id, app_identifier, depth + 1)
      else
        UI.error("Non-conflict error occurred. Upload failed: #{e.message}")
        return false
      end
    rescue => general_error
      UI.error("Unexpected error during upload: #{general_error.message}")
      UI.error(general_error.backtrace.join("\n"))
      return false
    end
  end
end