require "fastlane_core"
require "fastlane_core/helper"
require_relative "git"

module Fastlane
  class Github
    ENV_VARS = {
      # General workflow information
      workflow: "GITHUB_WORKFLOW",                            # Name of the workflow
      run_id: "GITHUB_RUN_ID",                                # Unique number for each workflow run within the repository
      run_number: "GITHUB_RUN_NUMBER",                        # Unique number for each run of a particular workflow in a repository
      run_attempt: "GITHUB_RUN_ATTEMPT",                      # Unique number for each attempt of a particular workflow run
      job: "GITHUB_JOB",                                      # Job ID of the current job
      action: "GITHUB_ACTION",                                # Unique identifier of the action currently running
      action_path: "GITHUB_ACTION_PATH",                      # Path where an action is located
      action_repository: "GITHUB_ACTION_REPOSITORY",          # Owner and repository name of the action
      actions: "GITHUB_ACTIONS",                              # Always set to true when GitHub Actions is running
      actor: "GITHUB_ACTOR",                                  # Name of the person or app that initiated the workflow
      actor_id: "GITHUB_ACTOR_ID",                            # Account ID of the person or app that triggered the initial workflow run
      triggering_actor: "GITHUB_TRIGGERING_ACTOR",            # Username of the user that initiated the workflow run

      # Repository information
      repository: "GITHUB_REPOSITORY",                        # Owner and repository name (e.g., octocat/Hello-World)
      repository_id: "GITHUB_REPOSITORY_ID",                  # ID of the repository
      repository_owner: "GITHUB_REPOSITORY_OWNER",            # Repository owner name
      repository_owner_id: "GITHUB_REPOSITORY_OWNER_ID",      # Account ID of the repository owner

      # Git reference information
      ref: "GITHUB_REF",                                      # Fully-formed ref of the branch or tag that triggered the workflow run
      ref_name: "GITHUB_REF_NAME",                            # Short ref name of the branch or tag that triggered the workflow run
      ref_protected: "GITHUB_REF_PROTECTED",                  # true if branch protections are configured for the ref
      ref_type: "GITHUB_REF_TYPE",                            # Type of ref that triggered the workflow run (branch or tag)
      sha: "GITHUB_SHA",                                      # Commit SHA that triggered the workflow

      # Pull request information
      head_ref: "GITHUB_HEAD_REF",                             # Head ref or source branch of the pull request in a workflow run
      base_ref: "GITHUB_BASE_REF",                             # Base ref or target branch of the pull request in a workflow run

      # Event information
      event_name: "GITHUB_EVENT_NAME",                         # Name of the event that triggered the workflow
      event_path: "GITHUB_EVENT_PATH",                         # Path to the complete webhook event payload on the runner

      # Server and API information
      server_url: "GITHUB_SERVER_URL",                          # URL of the GitHub server (default: https://github.com)
      api_url: "GITHUB_API_URL",                                # API URL (default: https://api.github.com)
      graphql_url: "GITHUB_GRAPHQL_URL",                        # GraphQL API URL (default: https://api.github.com/graphql)

      # Workspace and paths
      workspace: "GITHUB_WORKSPACE",                            # Default working directory on the runner for steps
      path: "GITHUB_PATH",                                      # System PATH variable
      env: "GITHUB_ENV",                                        # Path to file that contains environment variables

      # Output and step summary
      output: "GITHUB_OUTPUT",                                  # Path to file that contains job outputs from steps
      step_summary: "GITHUB_STEP_SUMMARY",                      # Path to file that contains step summary

      # Runner information
      runner_name: "RUNNER_NAME",                               # Name of the runner executing the job
      runner_os: "RUNNER_OS",                                   # Operating system of the runner executing the job
      runner_arch: "RUNNER_ARCH",                               # Architecture of the runner executing the job
      runner_temp: "RUNNER_TEMP",                               # Path to a temporary directory on the runner
      runner_tool_cache: "RUNNER_TOOL_CACHE",                   # Path to the directory containing preinstalled tools
      runner_debug: "RUNNER_DEBUG"                             # Set to 1 if debug logging is enabled
    }

    def self.env
      ENV_VARS.map { |key, envvar| [key, ENV[envvar]] }.reject { |_, value| value.nil? || value.empty? }.to_h
    end

    def self.org
      "suno-ai"
    end

    def self.repo(url: nil)
      url_or_default = url || `git remote get-url origin`.strip
      # Handle various GitHub URL formats
      match = url_or_default.match(/(?:https:\/\/github\.com\/|git@github\.com:)suno-ai\/([^\.]*)(?:\.git)?/)
      if match
        match[1]
      else
        raise "Invalid repo URL format: #{url_or_default}"
      end
    end

    def self.commit_link(sha)
      "https://github.com/#{org}/#{repo}/commit/#{sha}"
    end

    def self.branch_link(branch)
      "https://github.com/#{org}/#{repo}/tree/#{branch}"
    end

    def self.repo_url(repo: self.repo)
      "https://github.com/#{org}/#{repo}.git"
    end

    def self.diff_range(remote_name: "origin")
      github_event_name = Github.env[:event_name]
      github_base_ref = Github.env[:base_ref]
      github_head_ref = Github.env[:head_ref]

      if github_event_name == "pull_request" && github_base_ref && github_head_ref
        "#{remote_name}/#{github_base_ref}...#{remote_name}/#{github_head_ref}"
      elsif github_event_name == "push"
        "#{Git.previous_sha} #{Git.current_sha}"
      else
        "#{Git.default_branch_ref(remote_name: remote_name)}...HEAD"
      end
    end

    def self.branch
      env[:head_ref] || env[:ref_name]
    end

    def self.pr_number
      unless env[:ref]
        return nil
      end
      match = env[:ref].match(/^refs\/pull\/(?<pr_number>[0-9]+)\//)
      unless match
        return nil
      end
      match[:pr_number].to_i
    end

    def self.branch_sha(branch:, repo: self.repo)
      Actions.sh "gh api /repos/#{org}/#{repo}/git/refs/heads/#{branch} --jq .object.sha", log: true do |status, result, command|
        unless status.success?
          UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
        end
        sha = result.strip
        sha.empty? ? nil : sha
      end
    end

    def self.is_default_branch?
      branch == Git.default_branch
    end

    def self.get_commits_since(branch:, sha:, repo: self.repo)
      Actions.sh "gh api /repos/#{org}/#{repo}/compare/#{sha}...#{branch} --jq '.commits[].sha'", log: true do |status, result, command|
        unless status.success?
          UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
        end
        commits = result.strip.split("\n").reject(&:empty?)
        commits
      end
    end

    def self.get_pr(number:, repo: self.repo, fields: "number,title,body,headRefName,headRefOid,baseRefName,baseRefOid,url,author")
      Actions.sh "gh --repo #{org}/#{repo} pr view #{number} --json #{fields} 2>/dev/null || echo \"\"", log: true do |status, result, command|
        unless status.success?
          UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
        end
        pr = result.strip
        UI.user_error!("PR #{number} not found") if pr.empty?
        JSON.parse(pr, symbolize_names: true)
      rescue JSON::ParserError => e
        UI.user_error!("Failed to parse PR JSON: '#{pr}' - #{e.message}")
      end
    end

    def self.list_prs(head_ref: nil, state: nil, repo: self.repo, fields: "number,title,body,headRefName,headRefOid,baseRefName,baseRefOid,url,author")
      cmd = [
        "gh --repo #{org}/#{repo} pr list",
        state ? "--state #{state}" : nil,
        head_ref ? "--head #{head_ref}" : nil,
        "--json #{fields}"
      ].compact.join(" ")
      Actions.sh cmd, log: true do |status, result, command|
        unless status.success?
          UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
        end
        JSON.parse(result.strip, symbolize_names: true)
      rescue JSON::ParserError => e
        UI.user_error!("Failed to parse PR JSON: '#{pr}' - #{e.message}")
      end
    end

    def self.label_exists?(label:, repo: self.repo)
      Actions.sh "gh --repo #{org}/#{repo} label list --json name --jq .[] | grep -q \"#{label}\"", log: true do |status, result, command|
        unless status.success?
          UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
        end
        result.strip.empty?
      end
    end

    def self.get_changed_files(remote_name: "origin", range: nil, subdir: nil)
      Actions.sh "git diff --name-only #{range || Github.diff_range(remote_name: remote_name)} #{subdir ? "#{subdir}/" : ""}", log: true do |status, result, command|
        unless status.success?
          UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
        end
        result.strip.split("\n")
      end
    end

    def self.save_output(key:, value:)
      UI.message([
        "GITHUB_OUTPUT".cyan,
        ": #{key}=",
        "#{value}".green
      ].join(""))
      unless Github.env[:output]
        return
      end
      File.open(Github.env[:output], "a") do |file|
        file.puts("#{key}=#{value}")
      end
    end

    def self.save_summary(summary)
      UI.message("#{"GITHUB_STEP_SUMMARY".cyan}: ")
      UI.message(summary.green)
      unless Github.env[:step_summary]
        return
      end
      File.open(Github.env[:step_summary], "a") do |file|
        summary.each_line do |line|
          file.puts(line)
        end
      end
    end

    def self.log_error(message)
      if Github.env[:step_summary]
        # Print error to the console with annotations
        puts "::error::#{message}"
      else
        UI.message(message.red)
      end
    end

    def self.post_comment(pr_number:, body:, repo: self.repo, edit_last: false)
      Tempfile.open("comment.md") do |file|
        file.write(body)
        file.flush

        # Create new comment or edit last one
        UI.message("Creating new comment or editing last one")
        comment_url = `gh --repo #{org}/#{repo} pr comment #{pr_number} -F #{file.path} #{edit_last ? " --create-if-none --edit-last" : ""} 2>/dev/null || echo ''`.strip
        if comment_url.empty?
          UI.user_error!("Failed to extract comment ID from output")
          nil
        end
        UI.success("✅ Successfully created/updated comment (URL: #{comment_url})")
        comment_url
      end
      save_summary(body)
    end

    def self.post_status(sha:, context:, state:, description:, repo: self.repo, target_url: nil)
      unless ["pending", "success", "failure", "error"].include?(state)
        UI.user_error!("Invalid state: #{state}")
      end

      description = Shellwords.escape(description)
      target_url = Shellwords.escape(target_url)

      cmd = "gh api /repos/#{org}/#{repo}/statuses/#{sha} --method POST --field state=#{state} --field context=#{Shellwords.escape(context)}"
      cmd += " --field description=#{(description.length >= 137) ? "#{description[0..136]}..." : description}" if description
      cmd += " --field target_url=#{target_url}" if target_url

      Actions.sh(cmd, log: true)

      UI.success("✅ Created status check: #{context} (#{state})")
    end

    class Workflows
      DEFAULT_LIST_RUN_FIELDS = ["databaseId", "name", "url", "workflowName", "workflowDatabaseId", "headBranch", "headSha", "status", "createdAt", "updatedAt"]
      DEFAULT_VIEW_RUN_FIELDS = [*DEFAULT_LIST_RUN_FIELDS, "jobs"]
      WORKFLOW_PATH_REGEX = /^\.github\/workflows\/(?<name>[^\/.]+)\.yml$/

      def self.is_valid_path?(path)
        path.match?(WORKFLOW_PATH_REGEX)
      end

      def self.get_name_from_path(path)
        name = path.match(WORKFLOW_PATH_REGEX)&.[](:name)
        unless name
          UI.user_error!("Unable to extract workflow name from path: #{path}")
        end
        name
      end

      def self.get_runs(workflow_name:, branch: nil, sha: nil, status: nil, created_after: nil, limit: nil, repo: Github.repo, fields: DEFAULT_LIST_RUN_FIELDS)
        command = [
          "gh run list --repo #{Github.org}/#{repo} --json #{fields.join(",")} --workflow #{workflow_name}",
          (branch ? "--branch #{branch}" : nil),
          (sha ? "--commit #{sha}" : nil),
          (status ? "--status #{status}" : nil),
          (created_after ? "--created '>=#{Time.at(created_after).utc.iso8601}'" : nil),
          (limit ? "--limit #{limit}" : nil)
        ].compact.join(" ")
        Actions.sh command, log: true do |status, result, command|
          unless status.success?
            UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
          end
          JSON.parse(result.strip.empty? ? "[]" : result.strip, symbolize_names: true)
        rescue JSON::ParserError => e
          UI.user_error!("Failed to parse run JSON: '#{result.inspect}' - #{e.message}")
        end
      end

      def self.get_run(run_id:, repo: Github.repo, fields: DEFAULT_VIEW_RUN_FIELDS)
        Actions.sh "gh run view #{run_id} --repo #{Github.org}/#{repo} --json #{fields.join(",")} 2>/dev/null || echo \"\"", log: true do |status, result, command|
          unless status.success?
            UI.user_error!("Command #{command} (pid #{status.pid}) failed with status #{status.exitstatus}")
          end
          if result.strip.empty?
            return nil
          end
          JSON.parse(result.strip, symbolize_names: true)
        rescue JSON::ParserError => e
          UI.user_error!("Failed to parse run JSON: '#{result.inspect}' - #{e.message}")
        end
      end

      def self.download_artifacts(run_id:, download_dir:, name: nil, pattern: nil, repo: Github.repo)
        command = [
          "gh run download #{run_id} --repo #{Github.org}/#{repo} --dir #{download_dir}",
          (name ? "--name #{name}" : nil),
          (pattern ? "--pattern #{pattern}" : nil)
        ].compact.join(" ")
        Actions.sh(command, log: true)
        # list files in the directory
        Dir.glob(File.join(download_dir, "**", "*")).select { |f| File.file?(f) }
      end
    end
  end
end
