# frozen_string_literal: true

module Rack
  module BearerAuth
    class MatchPattern
      attr_reader :path, :via, :token

      def initialize(path, via, token)
        raise ArgumentError, "Token pattern is required" unless token

        @path = Path.new(path)
        @via = Via.new(via)
        @token = Token.new(token)
      end

      def match(req)
        return :skip unless match_route?(req)
        return :token_required unless req.token
        token.match?(req.token) ? :ok : :invalid_token
      end

      private

      def match_route?(req)
        path.match?(req.path) && via.match?(req.via)
      end

      class Base
        attr_reader :pattern

        def initialize(pattern)
          @pattern = pattern
        end

        def match?(*)
          raise ::NotImplementedError
        end

        def self.new(*)
          if self == Base
            raise ::NotImplementedError,
                  "#{self} is an abstract class and cannot be instantiated."
          end
          super
        end
      end

      class Path < Base
        def match?(path)
          _match?(self.pattern, path)
        end

        private

        def _match?(path_pattern, path_value)
          case path_pattern
          when nil
            true
          when String
            path_pattern == path_value
          when Regexp
            !(path_pattern =~ path_value).nil?
          when Proc
            path_pattern.call(path_value)
          when Array
            path_pattern.any? { |pattern| _match?(pattern, path_value) }
          else
            raise "Unsupported path pattern"
          end
        end
      end

      class Via < Base
        def match?(via)
          _match?(self.pattern, via)
        end

        private

        def _match?(via_pattern, via_value)
          case via_pattern
          when nil, :all
            true
          when Symbol, String
            via_pattern.to_sym == via_value
          when Regexp
            !(via_pattern =~ via_value).nil?
          when Proc
            via_pattern.call(via_value)
          when Array
            via_pattern.any? { |pattern| _match?(pattern, via_value) }
          else
            raise "Unsupported via pattern"
          end
        end
      end

      class Token < Base
        def match?(token)
          _match?(self.pattern, token)
        end

        private

        def _match?(token_pattern, token_value)
          case token_pattern
          when String
            token_pattern == token_value
          when Regexp
            !(token_pattern =~ token_value).nil?
          when Proc
            token_pattern.call(token_value)
          when Array
            token_pattern.any? { |pattern| _match?(pattern, token_value) }
          else
            raise "Unsupported token pattern"
          end
        end
      end
    end
  end
end