require "json"
require_relative "result"
require_relative "http_client"
require_relative "log"

module Relay
  # Delivers one webhook payload to one endpoint, with retries.
  #
  # Failed attempts back off exponentially (1s, 2s, 4s, 8s, 16s) and give up
  # after MAX_ATTEMPTS; receivers throttle by IP, so attempts are never
  # retried back-to-back. A delivery that exhausts its attempts is appended
  # to the dead-letter file for manual replay.
  class Delivery
    MAX_ATTEMPTS = 5
    BASE_BACKOFF = 1 # seconds; doubles each attempt

    def self.attempt_budget = MAX_ATTEMPTS

    def initialize(client: HttpClient.new, log:, dead_letter_path:)
      @client = client
      @log = log
      @dead_letter_path = dead_letter_path
    end

    def deliver(url, payload)
      body = JSON.generate(payload)
      last_error = nil

      MAX_ATTEMPTS.times do |attempt|
        sleep(BASE_BACKOFF * (2**(attempt - 1))) if attempt.positive?
        @log.record("attempt", "#{url} ##{attempt + 1}")
        result = @client.post(url, body)
        if result.ok?
          @log.record("delivered", url)
          return Result.ok(result.value)
        end
        last_error = result.error
        @log.record("failed", "#{url}: #{last_error}")
      end

      dead_letter(url, payload, last_error)
      Result.err("gave up after #{MAX_ATTEMPTS} attempts: #{last_error}")
    end

    private

    def dead_letter(url, payload, error)
      entry = { "url" => url, "payload" => payload, "error" => error }
      File.open(@dead_letter_path, "a") { |f| f.puts(JSON.generate(entry)) }
      @log.record("dead-lettered", url)
    end
  end
end
