require_relative "config"

module Backlog
  # Resolves a board name to its Planka board record, caching the resolved
  # id as `board_id=<id>` in the repo's `.cc-os/config` (issue #22).
  #
  # The tracker's board *name* stays the source of truth; the id is only a
  # cache. The cached id is honored only when the config's tracker is
  # exactly `planka:<board_name>`, and a cached `boards.get(id)` hit counts
  # only if the returned board's name still matches — a 404 or a rename
  # falls back to the projects+boards name lookup and rewrites the cache.
  # Any cache failure (unreadable config, unwritable config, stale id)
  # degrades to the name lookup; it never fails the command.
  #
  # Filesystem access is injected: `read_config` returns the raw
  # `.cc-os/config` contents (or nil), `write_config` persists updated
  # contents. Cache writes go through Config.set, the comment-preserving
  # writer — never Config.merge.
  class BoardResolver
    # @param client [Planka::Client] an already-authenticated client
    # @param read_config [#call] -> String, nil — raw .cc-os/config contents
    # @param write_config [#call] (String) -> void — persist updated contents
    def initialize(client:, read_config:, write_config:)
      @client = client
      @read_config = read_config
      @write_config = write_config
    end

    # A resolver with no config access: always name-lookup, never caches.
    def self.null(client:)
      new(client: client, read_config: -> {}, write_config: ->(_) {})
    end

    # @param name [String] the board name (source of truth)
    # @return [Planka::Types::Board]
    # @raise [RuntimeError] if no board with that name exists
    def board_for(name)
      cached = cached_board(name)
      return cached if cached

      board = lookup_by_name!(name)
      cache_id(name, board.id)
      board
    end

    private

    def cached_board(name)
      config = safe_config
      return nil unless config && config.tracker == "planka:#{name}"

      id = config.board_id
      return nil unless id

      board = @client.boards.get(id).board
      board && board.name == name ? board : nil
    rescue StandardError
      nil
    end

    def lookup_by_name!(name)
      @client.projects.list.each do |project|
        board = @client.boards.list(project.id).find { |b| b.name == name }
        return board if board
      end
      raise "no board named #{name.inspect} found (run board-ensure first)"
    end

    def cache_id(name, id)
      contents = @read_config.call
      config = Config.new(contents)
      return unless config.tracker == "planka:#{name}"
      return if config.board_id == id.to_s

      @write_config.call(Config.set(contents, "board_id", id))
    rescue StandardError
      nil # cache write failure never fails the command
    end

    def safe_config
      Config.new(@read_config.call)
    rescue StandardError
      nil
    end
  end
end
